使用SpringSecurity 進(jìn)行自定義Token校驗
背景
Spring Security默認(rèn)使用「用戶名/密碼」的方式進(jìn)行登陸校驗,并通過cookie的方式存留登陸信息。在一些定制化場景,比如希望單獨使用token串進(jìn)行部分頁面的訪問權(quán)限控制時,默認(rèn)方案無法支持。
在未能在網(wǎng)上搜索出相關(guān)實踐的情況下,通過官方文檔及個別Stack Overflow的零散案例,形成整體思路并實踐測試通過,本文即關(guān)于該方案的一個分享。
參考官方文檔
SpringSecurity校驗流程
基本的SpringSecurity使用方式網(wǎng)上很多,不是本文關(guān)注的重點。
關(guān)于校驗的整個流程簡單的說,整個鏈路有三個關(guān)鍵點,
- 將需要鑒權(quán)的類/方法/url),定義為需要鑒權(quán)(本文代碼示例為方法上注解@PreAuthorize("hasPermission('TARGET','PERMISSION')")
- 根據(jù)訪問的信息產(chǎn)生一個來訪者的權(quán)限信息Authentication,并插入到上下文中
- 在調(diào)用鑒權(quán)方法時,根據(jù)指定的鑒權(quán)方式,驗證權(quán)限信息是否符合權(quán)限要求
完整的調(diào)用鏈建議在IDE中通過單步調(diào)試親自體會,本文不做相關(guān)整理。
如何自定義
我的需求,是使用自定義的token,驗證權(quán)限,涉及到:
- 產(chǎn)生Authentication并插入到上下文中
- 針對token的驗證方式
需要做的事情如下:
- 自定義TokenAuthentication類,實現(xiàn)org.springframework.security.core.Authenticaion,作為token權(quán)限信息
- 自定義AuthenticationTokenFilter類,實現(xiàn)javax.servlet.Filter,在收到訪問時,根據(jù)訪問信息生成TokenAuthentication實例,并插入上下文
- 自定義SecurityPermissionEvalutor類,實現(xiàn)org.springframework.security.access.PermissionEvaluator,完成權(quán)限的自定義驗證邏輯
- 在全局的配置中,定義使用SecurityPermissionEvalutor作為權(quán)限校驗方式
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的自定義實現(xiàn)(用于校驗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實例,避免進(jìn)入SpringSecurity的用戶名密碼默認(rèn)模式
*/
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 自定義的權(quán)限處理模塊(鑒權(quán))
*/
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')")方式,不使用該鑒權(quán)邏輯
*/
return false;
}
private boolean hasPermission(String token,String targetDomain, String permission){
/**
* 驗證權(quán)限
**/
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配置,指定使用自定義的權(quán)限評估方法
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception{
return super.authenticationManager();
}
@Bean
public PermissionEvaluator permissionEvaluator() {
/**
* 使用自定義的權(quán)限驗證
**/
SecurityPermissionEvaluator securityPermissionEvaluator = new SecurityPermissionEvaluator();
return securityPermissionEvaluator;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
/**
* 關(guān)掉csrf方便本地ip調(diào)用調(diào)試
**/
httpSecurity
.csrf()
.disable()
.httpBasic()
.disable();
}
}
BaseRepository.java 某個需要權(quán)限驗證的方法
/**
* @author: Blaketairan
*/
import org.springframework.security.access.prepost.PreAuthorize;
import java.util.List;
/**
* Description:
*/
public interface BaseRepository{
@PreAuthorize("hasPermission('DOMAIN', 'PERMISSION')")
void deleteAll();
}
spring security 自定義token無法通過框架認(rèn)證
自定義token和refreshToken,如代碼所示:
UserDO userDO = userMapper.getByName(username);
UserDetails userDetails =
userService.loadUserByUsername(userForBase.getName());
String token = jwtTokenComponent.generateToken(userDO);
String refreshToken = jwtTokenComponent.generateRefreshToken(userDO);
storeToken(userDO, token,refreshToken);
jsonObject.put("principal", userDetails);
jsonObject.put("token_type", "bearer");
return jsonObject;
無法通過框架的認(rèn)證?搞它:
UserDO userDO = userMapper.getByName(username);
UserDetails userDetails =
userService.loadUserByUsername(userForBase.getName());
String token = jwtTokenComponent.generateToken(userDO);
String refreshToken = jwtTokenComponent.generateRefreshToken(userDO);
storeToken(userDO, token,refreshToken);
jsonObject.put("access_token", token);
jsonObject.put("refresh_token", refreshToken);
jsonObject.put("principal", userDetails);
jsonObject.put("token_type", "bearer");
return jsonObject;
private void storeToken(UserDO userDO, String token,String refreshToken) {
Map<String, String> tokenParams = new HashMap<>();
tokenParams.put("access_token", token);
tokenParams.put("expires_in", "7200");
tokenParams.put("token_type", "bearer");
OAuth2AccessToken oAuth2AccessToken = DefaultOAuth2AccessToken.valueOf(tokenParams);
DefaultOAuth2RefreshToken oAuth2RefreshToken = new DefaultOAuth2RefreshToken(refreshToken);
// 創(chuàng)建redisTemplate,序列化對象
Map<String, String> requestMap = new HashMap<>();
requestMap.put("username", userDO.getUsername());
requestMap.put("grant_type", "password");
Map<String, Object> queryMap = new HashMap<String, Object>();
queryMap.put("id",userDO.getUserId());
List<String> perms = menuMapper.listUserPerms(queryMap);
Set<GrantedAuthority> authorities = new HashSet<>();
for (String perm : perms) {
if (StringUtils.isNotBlank(perm)) {
authorities.add(new SimpleGrantedAuthority(perm.trim()));
}
}
OAuth2Request storedRequest = new OAuth2Request(requestMap, "oms-web", authorities, true, null,
null, null, null, null);
CustomUserDetails userEnhancer = new CustomUserDetails(userDO, true, true, true, true, authorities);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userEnhancer, null, userEnhancer.getAuthorities());
authentication.setDetails(requestMap);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(storedRequest, authentication);
tokenStore.storeAccessToken(oAuth2AccessToken, oAuth2Authentication);
tokenStore.storeRefreshToken(oAuth2RefreshToken, oAuth2Authentication);
}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring擴展之基于HandlerMapping實現(xiàn)接口灰度發(fā)布實例
這篇文章主要介紹了Spring擴展之基于HandlerMapping實現(xiàn)接口灰度發(fā)布實例,灰度發(fā)布是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式,灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時候就可以發(fā)現(xiàn)、調(diào)整問題,以保證其影響度,需要的朋友可以參考下2023-08-08
spring boot+自定義 AOP 實現(xiàn)全局校驗的實例代碼
最近公司重構(gòu)項目,重構(gòu)為最熱的微服務(wù)框架 spring boot, 重構(gòu)的時候遇到幾個可以統(tǒng)一處理的問題。這篇文章主要介紹了spring boot+自定義 AOP 實現(xiàn)全局校驗 ,需要的朋友可以參考下2019-04-04
Java 中Timer和TimerTask 定時器和定時任務(wù)使用的例子
這篇文章主要介紹了Java 中Timer和TimerTask 定時器和定時任務(wù)使用的例子,非常具有實用價值,需要的朋友可以參考下2017-05-05
SpringBoot對Druid配置SQL監(jiān)控功能失效問題及解決方法
這篇文章主要介紹了SpringBoot對Druid配置SQL監(jiān)控功能失效問題的解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-05-05
解決redisTemplate向redis中插入String類型數(shù)據(jù)時出現(xiàn)亂碼問題
這篇文章主要介紹了解決redisTemplate向redis中插入String類型數(shù)據(jù)時出現(xiàn)亂碼問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12

