Spring Security【一】 ------ 前后端分離開發


之前項目中都是使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM