使用SpringSecurity 進(jìn)行自定義Token校驗(yàn)
背景
Spring Security默認(rèn)使用「用戶名/密碼」的方式進(jìn)行登陸校驗(yàn),并通過(guò)cookie的方式存留登陸信息。在一些定制化場(chǎng)景,比如希望單獨(dú)使用token串進(jìn)行部分頁(yè)面的訪問(wèn)權(quán)限控制時(shí),默認(rèn)方案無(wú)法支持。
在未能在網(wǎng)上搜索出相關(guān)實(shí)踐的情況下,通過(guò)官方文檔及個(gè)別Stack Overflow的零散案例,形成整體思路并實(shí)踐測(cè)試通過(guò),本文即關(guān)于該方案的一個(gè)分享。
參考官方文檔
SpringSecurity校驗(yàn)流程
基本的SpringSecurity使用方式網(wǎng)上很多,不是本文關(guān)注的重點(diǎn)。
關(guān)于校驗(yàn)的整個(gè)流程簡(jiǎn)單的說(shuō),整個(gè)鏈路有三個(gè)關(guān)鍵點(diǎn),
- 將需要鑒權(quán)的類/方法/url),定義為需要鑒權(quán)(本文代碼示例為方法上注解@PreAuthorize("hasPermission('TARGET','PERMISSION')")
- 根據(jù)訪問(wèn)的信息產(chǎn)生一個(gè)來(lái)訪者的權(quán)限信息Authentication,并插入到上下文中
- 在調(diào)用鑒權(quán)方法時(shí),根據(jù)指定的鑒權(quán)方式,驗(yàn)證權(quán)限信息是否符合權(quán)限要求
完整的調(diào)用鏈建議在IDE中通過(guò)單步調(diào)試親自體會(huì),本文不做相關(guān)整理。
如何自定義
我的需求,是使用自定義的token,驗(yàn)證權(quán)限,涉及到:
- 產(chǎn)生Authentication并插入到上下文中
- 針對(duì)token的驗(yàn)證方式
需要做的事情如下:
- 自定義TokenAuthentication類,實(shí)現(xiàn)org.springframework.security.core.Authenticaion,作為token權(quán)限信息
- 自定義AuthenticationTokenFilter類,實(shí)現(xiàn)javax.servlet.Filter,在收到訪問(wèn)時(shí),根據(jù)訪問(wèn)信息生成TokenAuthentication實(shí)例,并插入上下文
- 自定義SecurityPermissionEvalutor類,實(shí)現(xiàn)org.springframework.security.access.PermissionEvaluator,完成權(quán)限的自定義驗(yàn)證邏輯
- 在全局的配置中,定義使用SecurityPermissionEvalutor作為權(quán)限校驗(yà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的自定義實(shí)現(xiàn)(用于校驗(yà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實(shí)例 */ @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時(shí),至少塞入空TokenAuthenticaion實(shí)例,避免進(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){ /** * 驗(yàn)證權(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)限評(píng)估方法 */ @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)限驗(yà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 某個(gè)需要權(quán)限驗(yà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無(wú)法通過(guò)框架認(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;
無(wú)法通過(guò)框架的認(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,序列化對(duì)象 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); }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java如何用反射將一個(gè)對(duì)象復(fù)制給另一個(gè)對(duì)象
這篇文章主要介紹了java如何用反射將一個(gè)對(duì)象復(fù)制給另一個(gè)對(duì)象問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09Spring擴(kuò)展之基于HandlerMapping實(shí)現(xiàn)接口灰度發(fā)布實(shí)例
這篇文章主要介紹了Spring擴(kuò)展之基于HandlerMapping實(shí)現(xiàn)接口灰度發(fā)布實(shí)例,灰度發(fā)布是指在黑與白之間,能夠平滑過(guò)渡的一種發(fā)布方式,灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時(shí)候就可以發(fā)現(xiàn)、調(diào)整問(wèn)題,以保證其影響度,需要的朋友可以參考下2023-08-08spring boot+自定義 AOP 實(shí)現(xiàn)全局校驗(yàn)的實(shí)例代碼
最近公司重構(gòu)項(xiàng)目,重構(gòu)為最熱的微服務(wù)框架 spring boot, 重構(gòu)的時(shí)候遇到幾個(gè)可以統(tǒng)一處理的問(wèn)題。這篇文章主要介紹了spring boot+自定義 AOP 實(shí)現(xiàn)全局校驗(yàn) ,需要的朋友可以參考下2019-04-04spring boot 配置Filter過(guò)濾器的方法
本篇文章主要介紹了spring boot 配置Filter過(guò)濾器的方法,實(shí)例分析了spring boot 配置Filter過(guò)濾器的技巧,有興趣的可以了解一下。2017-03-03Java 中Timer和TimerTask 定時(shí)器和定時(shí)任務(wù)使用的例子
這篇文章主要介紹了Java 中Timer和TimerTask 定時(shí)器和定時(shí)任務(wù)使用的例子,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05SpringBoot對(duì)Druid配置SQL監(jiān)控功能失效問(wèn)題及解決方法
這篇文章主要介紹了SpringBoot對(duì)Druid配置SQL監(jiān)控功能失效問(wèn)題的解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05

解決redisTemplate向redis中插入String類型數(shù)據(jù)時(shí)出現(xiàn)亂碼問(wèn)題