標籤:pos auto app use 調整 str framework 避免 dmi
最近在項目中遇到了這樣一個問題:前後端分離,前端用Vue來做,所有的資料請求都使用vue-resource,沒有使用表單,因此資料互動都是使用JSON,後台使用Spring Boot,許可權驗證使用了Spring Security,因為之前用Spring Security都是處理頁面的,這次單純處理Ajax請求,因此記錄下遇到的一些問題。這裡的解決方案不僅適用於Ajax請求,也可以解決移動端請求驗證。
本文是V部落項目在完成過程中的問題記錄,完整項目請小夥伴們移步這裡:https://github.com/lenve/VBlog建立工程
首先我們需要建立一個Spring Boot工程,建立時需要引入Web、Spring Security、MySQL和MyBatis(資料庫架構其實隨意,我這裡使用MyBatis),建立好之後,依賴檔案如下:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency><dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.11</version></dependency>
注意最後一個commons-codec依賴是我手動加入進來的,這是一個Apache的開源項目,可以用來產生MD5訊息摘要,我在後文中將對密碼進行簡單的處理。
建立資料庫並配置
為了簡化邏輯,我這裡建立了三個表,分別是使用者表、角色表、使用者角色關聯表,如下:
接下來我們需要在application.properties中對自己的資料庫進行簡單的配置,這裡各位小夥伴視自己的具體情況而定。
spring.datasource.url=jdbc:mysql:///vueblogspring.datasource.username=rootspring.datasource.password=123
構造實體類
這裡主要是指構造使用者類,這裡的使用者類比較特殊,必須實現UserDetails介面,如下:
public class User implements UserDetails { private Long id; private String username; private String password; private String nickname; private boolean enabled; private List<Role> roles; @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } @Override public List<GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())); } return authorities; } //getter/setter省略...}
實現了UserDetails介面之後,該介面中有幾個方法需要我們實現,四個返回Boolean的方法都是見名知意,enabled表示檔期賬戶是否啟用,這個我資料庫中確實有該欄位,因此根據查詢結果返回,其他的為了簡單期間都直接返回true,getAuthorities方法返回目前使用者的角色資訊,使用者的角色其實就是roles中的資料,將roles中的資料轉換為List之後返回即可,這裡有一個要注意的地方,由於我在資料庫中儲存的角色名稱都是諸如‘超級管理員’、‘普通使用者’之類的,並不是以ROLE_
這樣的字元開始的,因此需要在這裏手動加上ROLE_
,切記。
另外還有一個Role實體類,比較簡單,按照資料庫的欄位建立即可,這裡不再贅述。
建立UserService
這裡的UserService也比較特殊,需要實現UserDetailsService介面,如下:
@Servicepublic class UserService implements UserDetailsService { @Autowired UserMapper userMapper; @Autowired RolesMapper rolesMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { User user = userMapper.loadUserByUsername(s); if (user == null) { //避免返回null,這裡返回一個不含有任何值的User對象,在後期的密碼比對過程中一樣會驗證失敗 return new User(); } //查詢使用者的角色資訊,並返回存入user中 List<Role> roles = rolesMapper.getRolesByUid(user.getId()); user.setRoles(roles); return user; }}
實現了UserDetailsService介面之後,我們需要實現該介面中的loadUserByUsername方法,即根據使用者名稱查詢使用者。這裡注入了兩個MyBatis中的Mapper,UserMapper用來查詢使用者,RolesMapper用來查詢角色。在loadUserByUsername方法中,首先根據傳入的參數(參數就是使用者登入時輸入的使用者名稱)去查詢使用者,如果查到的使用者為null,可以直接拋一個UsernameNotFoundException異常,但是我為了處理方便,返回了一個沒有任何值的User對象,這樣在後面的密碼比對過程中一樣會發現登入失敗的(這裡大家根據自己的業務需求調整即可),如果查到的使用者不為null,此時我們根據查到的使用者id再去查詢該使用者的角色,並將查詢結果放入到user對象中,這個查詢結果將在user對象的getAuthorities方法中用上。
Security配置
我們先來看一下我的Security配置,然後我再來一一解釋:
@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() { @Override public String encode(CharSequence charSequence) { return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()); } /** * @param charSequence 明文 * @param s 密文 * @return */ @Override public boolean matches(CharSequence charSequence, String s) { return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes())); } }); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("超級管理員") .anyRequest().authenticated()//其他的路徑都是登入後即可訪問 .and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); out.write("{\"status\":\"ok\",\"msg\":\"登入成功\"}"); out.flush(); out.close(); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"登入失敗\"}"); out.flush(); out.close(); } }).loginProcessingUrl("/login") .usernameParameter("username").passwordParameter("password").permitAll() .and().logout().permitAll().and().csrf().disable(); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/reg"); }}
這是我們配置的核心,小夥伴們聽我一一道來:
1.首先這是一個配置類,因此記得加上@Configuration註解,又因為這是Spring Security的配置,因此記得繼承WebSecurityConfigurerAdapter。
2.將剛剛建立好的UserService注入進來,一會我們要用。
3.configure(AuthenticationManagerBuilder auth)方法中用來配置我們的認證方式,在auth.userDetailsService()方法中傳入userService,這樣userService中的loadUserByUsername方法在使用者登入時將會被自動調用。後面的passwordEncoder是可選項,可寫可不寫,因為我是將使用者的純文字密碼產生了MD5訊息摘要後存入資料庫的,因此在登入時也需要對純文字密碼進行處理,所以就加上了passwordEncoder,加上passwordEncoder後,直接new一個PasswordEncoder匿名內部類即可,這裡有兩個方法要實現,看名字就知道方法的含義,第一個方法encode顯然是對明文進行加密,這裡我使用了MD5訊息摘要,具體的實現方法是由commons-codec依賴提供的;第二個方法matches是密碼的比對,兩個參數,第一個參數是純文字密碼,第二個是密文,這裡只需要對明文加密後和密文比較即可(小夥伴如果對此感興趣可以繼續考慮密碼加鹽)。
4.configure(HttpSecurity http)用來配置我們的認證規則等,authorizeRequests方法表示開啟了認證規則配置,antMatchers(“/admin/**”).hasRole(“超級管理員”)表示/admin/**
的路徑需要有‘超級管理員’角色的使用者才能訪問,我在網上看到小夥伴對hasRole方法中要不要加ROLE_
首碼有疑問,這裡是不要加的,如果用hasAuthority方法才需要加。anyRequest().authenticated()表示其他所有路徑都是需要認證/登入後才能訪問。接下來我們配置了登入頁面為login_page,登入處理路徑為/login,登入使用者名稱為username,密碼為password,並配置了這些路徑都可以直接存取,登出登陸也可以直接存取,最後關閉csrf。在successHandler中,使用response返回登入成功的json即可,切記不可以使用defaultSuccessUrl,defaultSuccessUrl是只登入成功後重新導向的頁面,failureHandler也是由於相同的原因不使用failureUrl。
5.configure(WebSecurity web)方法中我配置了一些過濾規則,不贅述。
6.另外,對於靜態檔案,如/images/**
、/css/**
、/js/**
這些路徑,這裡預設都是不攔截的。
Controller
最後來看看我們的Controller,如下:
@RestControllerpublic class LoginRegController { /** * 如果自動跳轉到這個頁面,說明使用者未登入,返回相應的提示即可 * <p> * 如果要支援表單登入,可以在這個方法中判斷請求的類型,進而決定返回JSON還是HTML頁面 * * @return */ @RequestMapping("/login_page") public RespBean loginPage() { return new RespBean("error", "尚未登入,請登入!"); }}
這個Controller整體來說還是比較簡單的,RespBean一個響應bean,返回一段簡單的json,不贅述,這裡需要小夥伴注意的是login_page
,我們配置的登入頁面是一個login_page
,但實際上login_page
並不是一個頁面,而是返回一段JSON,這是因為當我未登入就去訪問其他頁面時Spring Security會自動跳轉到到login_page
頁面,但是在Ajax請求中,不需要這種跳轉,我要的只是是否登入的提示,所以這裡返回json即可。
測試
最後小夥伴可以使用POSTMAN或者RESTClient等工具來測試登入和許可權問題,我就不示範了。
Ok,經過上文的介紹,想必小夥伴們對Spring Boot+Spring Security處理Ajax登入請求已經有所瞭解了,好了,本文就說到這裡,有問題歡迎留言討論。
本文是V部落項目在完成過程中的問題記錄,完整項目請小夥伴們移步這裡:https://github.com/lenve/VBlog
更多資料請關注公眾號:
SpringBoot+SpringSecurity處理Ajax登入請求