使用SpringSecurity|使用SpringSecurity 进行自定义Token校验
背景
Spring Security默认使用「用户名/密码」的方式进行登陆校验,并通过cookie的方式存留登陆信息。在一些定制化场景,比如希望单独使用token串进行部分页面的访问权限控制时,默认方案无法支持。
在未能在网上搜索出相关实践的情况下,通过官方文档及个别Stack Overflow的零散案例,形成整体思路并实践测试通过,本文即关于该方案的一个分享。
参考官方文档
SpringSecurity校验流程
基本的SpringSecurity使用方式网上很多,不是本文关注的重点。
关于校验的整个流程简单的说,整个链路有三个关键点,
- 将需要鉴权的类/方法/url),定义为需要鉴权(本文代码示例为方法上注解@PreAuthorize("hasPermission('TARGET','PERMISSION')")
- 根据访问的信息产生一个来访者的权限信息Authentication,并插入到上下文中
- 在调用鉴权方法时,根据指定的鉴权方式,验证权限信息是否符合权限要求
如何自定义 我的需求,是使用自定义的token,验证权限,涉及到:
- 产生Authentication并插入到上下文中
- 针对token的验证方式
- 自定义TokenAuthentication类,实现org.springframework.security.core.Authenticaion,作为token权限信息
- 自定义AuthenticationTokenFilter类,实现javax.servlet.Filter,在收到访问时,根据访问信息生成TokenAuthentication实例,并插入上下文
- 自定义SecurityPermissionEvalutor类,实现org.springframework.security.access.PermissionEvaluator,完成权限的自定义验证逻辑
- 在全局的配置中,定义使用SecurityPermissionEvalutor作为权限校验方式
/** * @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; }@Overridepublic Collection extends GrantedAuthority> getAuthorities() {return new ArrayList(0); }@Overridepublic Object getCredentials(){return token; }@Overridepublic Object getDetails() {return null; }@Overridepublic Object getPrincipal() {return null; }@Overridepublic boolean isAuthenticated() {return true; }@Overridepublic void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {}@Overridepublic 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实例 */@Configurationpublic class AuthenticationTokenFilter implements Filter{@Overridepublic void init(FilterConfig filterConfig) throws ServletException{}@Overridepublic 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); }@Overridepublic 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 {@Overridepublic 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); }@Overridepublic 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@Overrideprotected AuthenticationManager authenticationManager() throws Exception{return super.authenticationManager(); }@Beanpublic PermissionEvaluator permissionEvaluator() {/*** 使用自定义的权限验证**/SecurityPermissionEvaluator securityPermissionEvaluator = new SecurityPermissionEvaluator(); return securityPermissionEvaluator; }@Overrideprotected 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(); }
spring security 自定义token无法通过框架认证 自定义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;
无法通过框架的认证?搞它:
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 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); // 创建redisTemplate,序列化对象Map requestMap = new HashMap<>(); requestMap.put("username", userDO.getUsername()); requestMap.put("grant_type", "password"); Map queryMap = new HashMap(); queryMap.put("id",userDO.getUserId()); List perms = menuMapper.listUserPerms(queryMap); Setauthorities = 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); }
【使用SpringSecurity|使用SpringSecurity 进行自定义Token校验】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
推荐阅读
- 由浅入深理解AOP
- 【译】20个更有效地使用谷歌搜索的技巧
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- iOS中的Block
- Linux下面如何查看tomcat已经使用多少线程
- 使用composer自动加载类文件
- android|android studio中ndk的使用
- 使用协程爬取网页,计算网页数据大小