SpringSecurity 進行自定義Token校驗
阿新 • • 發佈:2018-04-29
單獨 snapshot author 調試 wired vax net figure cas
背景
Spring Security默認使用「用戶名/密碼」的方式進行登陸校驗,並通過cookie的方式存留登陸信息。在一些定制化場景,比如希望單獨使用token串進行部分頁面的訪問權限控制時,默認方案無法支持。在未能在網上搜索出相關實踐的情況下,通過官方文檔及個別Stack Overflow的零散案例,形成整體思路並實踐測試通過,本文即關於該方案的一個分享。
參考
官方文檔:https://docs.spring.io/spring-security/site/docs/5.0.5.BUILD-SNAPSHOT/reference/htmlsingle/
SpringSecurity校驗流程
基本的SpringSecurity使用方式網上很多,不是本文關註的重點,如有需要可以自行搜索或參見https://blog.csdn.net/u012702547/article/details/54319508
關於校驗的整個流程簡單的說,整個鏈路有三個關鍵點,
- 將需要鑒權的類/方法/url),定義為需要鑒權(本文代碼示例為方法上註解@PreAuthorize("hasPermission(‘TARGET‘,‘PERMISSION‘)")
- 根據訪問的信息產生一個來訪者的權限信息Authentication,並插入到上下文中
- 在調用鑒權方法時,根據指定的鑒權方式,驗證權限信息是否符合權限要求
完整的調用鏈建議在IDE中通過單步調試親自體會,本文不做相關整理。
如何自定義
我的需求,是使用自定義的token,驗證權限,涉及到:
- 產生Authentication並插入到上下文中
- 針對token的驗證方式
需要做的事情如下:
- 自定義TokenAuthentication類,實現org.springframework.security.core.Authenticaion,作為token權限信息
- 自定義AuthenticationTokenFilter類,實現javax.servlet.Filter,在收到訪問時,根據訪問信息生成TokenAuthentication實例,並插入上下文
- 自定義SecurityPermissionEvalutor類,實現org.springframework.security.access.PermissionEvaluator,完成權限的自定義驗證邏輯
- 在全局的配置中,定義使用SecurityPermissionEvalutor作為權限校驗方式
TokenAuthentication.java
/**
* @author: Blaketairan
*/
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import java.util.ArrayList;
import java.util.Collection;
/**
* Description: spring-security的Authentication的自定義實現(用於校驗token)
*/
public class TokenAuthentication implements Authentication{
private String token;
public TokenAuthentication(String token){
this.token = token;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return new ArrayList<GrantedAuthority>(0);
}
@Override
public Object getCredentials(){
return token;
}
@Override
public Object getDetails() {
return null;
}
@Override
public Object getPrincipal() {
return null;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
@Override
public String getName() {
return null;
}
}
AuthenticationTokenFilter.java
/**
* @author: Blaketairan
*/
import com.google.common.base.Strings;
import com.blaketairan.spring.security.configuration.TokenAuthentication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Description: 用於處理收到的token並為spring-security上下文生成及註入Authenticaion實例
*/
@Configuration
public class AuthenticationTokenFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException{
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain)
throws IOException, ServletException{
if (servletRequest instanceof HttpServletRequest){
String token = ((HttpServletRequest) servletRequest).getHeader("PRIVATE-TOKEN");
if (!Strings.isNullOrEmpty(token)){
Authentication authentication = new TokenAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
System.out.println("Set authentication with non-empty token");
} else {
/**
* 在未收到Token時,至少塞入空TokenAuthenticaion實例,避免進入SpringSecurity的用戶名密碼默認模式
*/
Authentication authentication = new TokenAuthentication("");
SecurityContextHolder.getContext().setAuthentication(authentication);
System.out.println("Set authentication with empty token");
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy(){
}
}
SecurityPermissionEvalutor.java
/**
* @author: Blaketairan
*/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import java.io.Serializable;
/**
* Description: spring-security 自定義的權限處理模塊(鑒權)
*/
public class SecurityPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication,Object targetDomainObject, Object permission){
String targetDomainObjectString = null;
String permissionString = null;
String token = null;
try {
targetDomainObjectString = (String)targetDomainObject;
permissionString = (String)permission;
token = (String)authentication.getCredentials();
} catch (ClassCastException e){
e.printStackTrace();
return false;
}
return hasPermission(token, targetDomainObjectString, permissionString);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission){
/**
* 使用@PreAuthorize("hasPermission(‘TARGET‘,‘PERMISSION‘)")方式,不使用該鑒權邏輯
*/
return false;
}
private boolean hasPermission(String token,String targetDomain, String permission){
/**
* 驗證權限
**/
return true;
}
}
SecurityConfig.java 全局配置
/**
* @author: Blaketairan
*/
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Description: spring-security配置,指定使用自定義的權限評估方法
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception{
return super.authenticationManager();
}
@Bean
public PermissionEvaluator permissionEvaluator() {
/**
* 使用自定義的權限驗證
**/
SecurityPermissionEvaluator securityPermissionEvaluator = new SecurityPermissionEvaluator();
return securityPermissionEvaluator;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
/**
* 關掉csrf方便本地ip調用調試
**/
httpSecurity
.csrf()
.disable()
.httpBasic()
.disable();
}
}
BaseRepository.java 某個需要權限驗證的方法
/**
* @author: Blaketairan
*/
import org.springframework.security.access.prepost.PreAuthorize;
import java.util.List;
/**
* Description:
*/
public interface BaseRepository{
@PreAuthorize("hasPermission(‘DOMAIN‘, ‘PERMISSION‘)")
void deleteAll();
}
結語
希望對看到本文的人有所幫助。
SpringSecurity 進行自定義Token校驗