之前項目中都是使用shiro作為安全框架,但是很多資料推薦spring security作為spring項目的安全框架。既然用着spring,那spring security自然還是要了解下的。
但是在實際接觸中發現,比shori的學習成本高點。還有就是大部分都停留在將前端代碼放在后端路徑下,頁面的跳轉,重定向都由后端代碼實現。這在當下的前后端分離開發中還是不合適的,所以經過我這幾天的各種搗鼓,終於實現了speing security的前后端分離開發,后端主要處理接口數據,頁面全部由前端處理,傳輸使用JSON格式。
一、代碼結構
二、實現過程
spring security底層實現就是一串過濾器,因此我們需要重寫里面的一些方法,返回JSON格式。前端根據JSON數據進而實現用戶的登錄與身份驗證等操作。具體代碼如下
一、創建實體,實現 UserDetails接口,記得實現方法將boolean返回值改為true
@Setter @Getter public class BlogUser extends BaseEntity implements UserDetails { public BlogUser() { } public BlogUser(String userName, String userPassword) { this.userName = userName; this.userPassword = userPassword; } private static final Short ENABLE_FALSE = 0; /** * 用戶名 */ private String userName; /** * 密碼 */ private String userPassword; /** * 用戶的角色 * @return 角色組 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return this.userPassword; } @Override public String getUsername() { return this.userName; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return !this.enable.equals(ENABLE_FALSE); }
二、編寫DAO層代碼,實現UserDetailsService,重寫loadUserByUsername方法
@Component public class SelfUserDetailsServiceImpl implements UserDetailsService { @Autowired private BlogUserMapper mapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { BlogUser blogUser = mapper.loadUserByUsername(username); if(ObjectUtils.isEmpty(blogUser)){ throw new UsernameNotFoundException("根據用戶名未找到用戶信息"); } return blogUser; } }
到此為止,security框架需要你做的事情已經完了。它會有一個默認登錄頁面(很丑,一看就是后端寫的)。封裝的還是很強大的。同時還支持前后端分離配置,不得不佩服spring的強大,下面看具體實現。
三、首先先看下security的配置類
@EnableWebSecurity @Configuration public class WebSecurityConfigure extends WebSecurityConfigurerAdapter { /** * 自定義登錄認證 */ @Autowired private SelfAuthenticationProvider authenticationProvider; /** * 自定義登錄成功處理器 */ @Autowired private UrlAuthenticationSuccessHandler authenticationSuccessHandler; /** * 自定義登錄失敗處理器 */ @Autowired private UrlAuthenticationFailureHandler authenticationFailureHandler; /** * 自定義注銷處理器 */ @Autowired private UrlLogoutSuccessHandler logoutSuccessHandler; /** * 登錄認證 * @param auth 登陸管理器 */ @Override protected void configure(AuthenticationManagerBuilder auth) { //添加自定義登陸認證 auth.authenticationProvider(authenticationProvider); } /** * 具體配置登陸細節 * @param http 登陸訪問對象 * @throws Exception 登陸異常 */ @Override protected void configure(HttpSecurity http) throws Exception { //關閉csrf http.csrf().disable() //關閉Session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() //開放api路徑 .authorizeRequests().antMatchers("/api/**","/five-service/blog-article/search/**","/five-service/blog-article/point","/five-service/blog-user/login"). permitAll() .anyRequest().authenticated() //開啟自動配置的登陸功能 .and() //自定義登錄請求路徑(post請求) .formLogin().usernameParameter("userName").passwordParameter("userPassword") .loginProcessingUrl("/five-service/login") //驗證成功處理器 .successHandler(authenticationSuccessHandler) //驗證失敗處理器 .failureHandler(authenticationFailureHandler).permitAll() .and() //關閉攔截未登錄自動跳轉,改為返回json信息 .exceptionHandling().authenticationEntryPoint(selfLoginUrlAuthenticationEntryPoint()) //開啟自動配置的注銷功能 .and() .logout() .logoutUrl("/five-service/logout") //注銷成功處理器 .logoutSuccessHandler(logoutSuccessHandler).permitAll() .and() //添加token過濾器 .addFilter(new TokenAuthenticationFilter(authenticationManagerBean())); } /** * 身份認證失敗處理類 * @return AuthenticationEntryPoint */ @Bean public AuthenticationEntryPoint selfLoginUrlAuthenticationEntryPoint() { return new SelfLoginUrlAuthenticationEntryPoint("/"); } /** * 重寫方法,是上下文可以獲取本地緩存對象 * @return AuthenticationManager 本地緩存對象 * @throws Exception 異常 */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
首先是自定義登錄處理 SelfAuthenticationProvider
@Component public class SelfAuthenticationProvider implements AuthenticationProvider { //DAO查詢用戶 @Autowired private SelfUserDetailsServiceImpl userDetailsService; //密碼加密解密 @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { //表單輸入的用戶名 String username = (String) authentication.getPrincipal(); //表單輸入的密碼 String password = (String) authentication.getCredentials(); UserDetails userDetails = userDetailsService.loadUserByUsername(username); //對加密密碼進行驗證 if(bCryptPasswordEncoder.matches(password,userDetails.getPassword())){ return new UsernamePasswordAuthenticationToken(username,password,null); }else { throw new BadCredentialsException("密碼錯誤"); } } @Override public boolean supports(Class<?> aClass) { return true; }
然后是登陸成功處理類 UrlAuthenticationSuccessHandler
@Component public class UrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException { //security在分布式環境token使用,下一章會寫道 Cookie token = TokenUtils.createToken(httpServletRequest); httpServletResponse.addCookie(token); httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8"); httpServletResponse.setStatus(200); PrintWriter writer = httpServletResponse.getWriter(); writer.write(HttpResult.getJsonResult(200,"登陸成功")); writer.flush(); writer.close(); } }
然后是登錄失敗處理類 UrlAuthenticationFailureHandler
@Component public class UrlAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setStatus(401); PrintWriter writer = httpServletResponse.getWriter(); writer.write(HttpResult.getJsonResult(401,"登陸失敗")); writer.flush(); writer.close(); } }
最后是注銷成功處理類
@Component public class UrlLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setStatus(200); PrintWriter writer = httpServletResponse.getWriter(); writer.write(HttpResult.getJsonResult(100,"注銷成功")); writer.flush(); writer.close(); } }
至此后端代碼就全部完成,前端根據JSON解析來完成登錄及身份驗證的全部動作。在配置類中可以看到我開啟禁用session配置,目的是將用戶信息存入redis,實現分布式身份驗證需求。在單機環境下,可以開啟session(默認開啟),同時在過濾鏈中將自定義的TokenAuthenticationFilter去掉即可。
原文鏈接:https://blog.csdn.net/qq314499182/article/details/87913202