上篇 Spring Security 登录校验 源码解析  分析了使用Spring Security时用户登录时验证并返回token过程,本篇分析下用户带token访问时,如何验证用户登录状态及权限问题

用户访问控制相对简单,本质同登录验证一样,均采用过滤器拦截请求进行验证

这里需要自定义过滤器JwtAuthenticationTokenFilter并自定义路径匹配器RequestMatcher ,JwtAuthenticationTokenFilter继承AbstractAuthenticationProcessingFilter,并实现attemptAuthentication、successfulAuthentication、unsuccessfulAuthentication方法

1 AbstractAuthenticationProcessingFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//判断访问是否需要过滤
if(!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
} Authentication authResult;
//调用子类JwtAuthenticationTokenFilter实现
try {
authResult = this.attemptAuthentication(request, response);
if(authResult == null) {
return;
}
//session控制策略,因为用jwt,故不进行深入分析
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
//验证失败,调用子类unsuccessfulAuthentication方法
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
//验证失败,调用子类unsuccessfulAuthentication方法
this.unsuccessfulAuthentication(request, response, var9);
return;
} if(this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//验证成功,调用子类successfulAuthentication方法
this.successfulAuthentication(request, response, chain, authResult);
}
}

2 JwtAuthenticationTokenFilter

@Component
public class JwtAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter { @Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private ObjectMapper mapper; @Value("${jwt.header}")
private String tokenHeader; @Value("${jwt.tokenHead}")
private String tokenHead; @Value("${jwt.appExpiration}")
private Long appExpiration; @Value("${jwt.pcExpiration}")
private Long pcExpiration; public JwtAuthenticationTokenFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationManager(authentication -> authentication);
} @Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
//获得header
String authHeader = httpServletRequest.getHeader(this.tokenHeader);
throwException(authHeader == null,
new TokenVerifyException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌缺失")); throwException(!authHeader.startsWith(this.tokenHead),
new TokenVerifyException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确")); String authToken = authHeader.substring(this.tokenHead.length());
UserDetails userDetails;
try {
Claims claims = jwtTokenUtil.getClaimsFromToken(authToken); throwException(claims.getSubject() == null,
new TokenVerifyException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确")); // 校验密码是否已修改
userDetails = this.verifyPasswordChanged(claims.getSubject(), claims.getIssuedAt()); } catch (ExpiredJwtException ex) {
this.refreshToken(httpServletResponse, authToken);
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_ACCESS_KEY_NOT_EFFECT, "令牌已过期");
} catch (UnsupportedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌错误");
} catch (MalformedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确");
} catch (SignatureException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "签名错误");
} Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
((UsernamePasswordAuthenticationToken) authentication).setDetails(
new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
}
return this.getAuthenticationManager().authenticate(authentication);
} private void refreshToken(HttpServletResponse response, String oldToken) {
try {
Claims claims = jwtTokenUtil.getClaimsAllowedClockSkew(oldToken, Integer.MAX_VALUE); String username = claims.getSubject();
this.verifyPasswordChanged(username, claims.getIssuedAt()); String newToken;
if (JwtUser.LOGIN_WAY_APP.equals(claims.getAudience())) {
newToken = jwtTokenUtil.refreshToken(oldToken, appExpiration);
} else if (JwtUser.LOGIN_WAY_PC.equals(claims.getAudience())) {
newToken = jwtTokenUtil.refreshToken(oldToken, pcExpiration);
} else {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "未识别的客户端");
} response.addHeader(this.tokenHeader, this.tokenHead + newToken); } catch (ExpiredJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_RELOGIN, "登录已过期");
} catch (UnsupportedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌错误");
} catch (MalformedJwtException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "令牌格式不正确");
} catch (SignatureException ex) {
throw new TokenParseException(RESPONSE_CODE.BACK_CODE_UNAUTHORIZED, "签名错误");
}
} //验证密码是否变更
private UserDetails verifyPasswordChanged(String username, Date tokenCreateTime) {
JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.isCreatedBeforeLastPasswordReset(tokenCreateTime, user.getLastPasswordResetDate())) {
throw new TokenVerifyException(RESPONSE_CODE.BACK_CODE_RELOGIN, "密码变更,需重新登录");
}
return user;
} private void throwException(boolean expression, AuthenticationException ex) {
if (expression) {
throw ex;
}
} //认证结果放入缓存
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
} //认证失败,封装返回信息
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
BackResult<String> result = new BackResult<>();
result.setCode(RESPONSE_CODE.BACK_CODE_EXCEPTION.value); if (failed instanceof TokenVerifyException) {
TokenVerifyException ex = (TokenVerifyException) failed;
result.setCode(ex.getResponseCode().value);
result.setExceptions(ex.getMessage()); } else if (failed instanceof TokenParseException) {
TokenParseException ex = (TokenParseException) failed;
result.setCode(ex.getResponseCode().value);
result.setExceptions(ex.getMessage()); } else {
logger.error("Token校验异常", failed);
result.setExceptions(SystemConstant.HIDE_ERROR_TIP);
} response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
mapper.writeValue(response.getWriter(), result);
}
}

3 SkipPathRequestMatcher

public class SkipPathRequestMatcher implements RequestMatcher {
private OrRequestMatcher matchers;
private RequestMatcher processingMatcher; public SkipPathRequestMatcher(@NotEmpty List<String> pathsToSkip, String processingPath) {
List<RequestMatcher> m = pathsToSkip.stream().map(AntPathRequestMatcher::new).collect(Collectors.toList()); matchers = new OrRequestMatcher(m);
processingMatcher = new AntPathRequestMatcher(processingPath);
} @Override
public boolean matches(HttpServletRequest request) {
return !matchers.matches(request) && processingMatcher.matches(request);
}
} WebSecurityConfig 类中定义SkipPathRequestMatcher
public static final String TOKEN_BASED_ENTRY_POINT = "/limit/**";
public static final String TOKEN_AUTH_ENTRY_POINT = "/auth/**";
public static final String TOKEN_LOGIN_ENTRY_POINT = "/login/**";
public static final String TOKEN_OPEN_ENTRY_POINT = "/open/**"; //路径匹配器,跳过/auth/**类请求,验证/limit/**类请求
@Bean
public SkipPathRequestMatcher skipPathRequestMatcher() {
List<String> pathsToSkip = Collections.singletonList(TOKEN_AUTH_ENTRY_POINT);
return new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_ENTRY_POINT);
} //JwtAuthenticationTokenFilter设置路径匹配器
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter(skipPathRequestMatcher());
}

Spring Security 访问控制 源码解析的更多相关文章

  1. Spring Security 解析(七) —— Spring Security Oauth2 源码解析

    Spring Security 解析(七) -- Spring Security Oauth2 源码解析   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因 ...

  2. spring security 认证源码跟踪

    spring security 认证源码跟踪 ​ 在跟踪认证源码之前,我们先根据官网说明一下security的内部原理,主要是依据一系列的filter来实现,大家可以根据https://docs.sp ...

  3. spring boot @Value源码解析

    Spring boot 的@Value只能用于bean中,在bean的实例化时,会给@Value的属性赋值:如下面的例子: @SpringBootApplication @Slf4j public c ...

  4. Feign 系列(05)Spring Cloud OpenFeign 源码解析

    Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...

  5. 异步任务spring @Async注解源码解析

    1.引子 开启异步任务使用方法: 1).方法上加@Async注解 2).启动类或者配置类上@EnableAsync 2.源码解析 虽然spring5已经出来了,但是我们还是使用的spring4,本文就 ...

  6. Spring @Import注解源码解析

    简介 Spring 3.0之前,创建Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内.而在Spring 3.0之后提供了JavaConfig的方式,也就是将IO ...

  7. spring security 实践 + 源码分析

    前言 本文将从示例.原理.应用3个方面介绍 spring data jpa. 以下分析基于spring boot 2.0 + spring 5.0.4版本源码 概述 Spring Security 是 ...

  8. 社交媒体登录Spring Social的源码解析

    在上一篇文章中我们给大家介绍了OAuth2授权标准,并且着重介绍了OAuth2的授权码认证模式.目前绝大多数的社交媒体平台,都是通过OAuth2授权码认证模式对外开放接口(登录认证及用户信息接口等). ...

  9. api网关揭秘--spring cloud gateway源码解析

    要想了解spring cloud gateway的源码,要熟悉spring webflux,我的上篇文章介绍了spring webflux. 1.gateway 和zuul对比 I am the au ...

随机推荐

  1. RFID NFC

    RFID 和 NFC 的区别: ()NFC与RFID在物理层面看上去很相似,但实际上是两个完全不同的领域,因为RFID本质上属于识别技术,而NFC属于通信技术. ()NFC将非接触读卡器.非接触卡和点 ...

  2. python的学习笔记01_6练习

    # 一.[用户登陆程序]# 基础需求:# 让用户输入用户名密码# 认证成功后显示欢迎信息# 输错三次后退出程序 count = 0 name = "cheng" password ...

  3. CAD 在ARCGIS中的坐标系问题

    近期在使用服务(文本写入dxf方式)导出CAD的时候发现导出的CAD文件和原始数据在ArcMap中叠加后不能重合,出现了错位的现象. 查看CAD文件后发现CAD的坐标系及投影和数据不一致导致的.遇到这 ...

  4. 44.Odoo产品分析 (五) – 定制板块(1) – 管理odoo安装(1)

    查看Odoo产品分析系列--目录 1 管理员的注意事项 在记录重要的配置细节时必须要小心,而且必须要有一个连续性的合适的.让系统能够安装备份并运行在一个可接受的时间内的计划. 1.1 制定实施策略 如 ...

  5. 未找到导入的项目“C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\DotNet\Microsoft.DotNet.Props”

    未找到导入的项目“C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\DotNet\Microsoft.DotNet.Props” ...

  6. 【视频】设计模式(C++)视频讲解

    设计模式(C++) 视频网址: http://www.qghkt.com/ 设计模式(C++)视频地址: https://ke.qq.com/course/318637?tuin=a508ea62 目 ...

  7. SQL Server创建Job, 实现执行相同脚本而产生不同作业计划的探究

    1 . 背景描述 本公司的SQL Server 服务器近百台,为了收集服务器运行的状态,需要在各个实例上部署监控Job,将收集到的信息推送到中央管理服务器. 收集的信息主要包括:慢查询.阻塞.资源等待 ...

  8. redis数据库安装 redis持久化及主从复制

    ----------------------------------------安装redis-5.0.4---------------------------------------- wget h ...

  9. July 11th, 2018. Wednesday, Week 28th.

    It is during our darkest moments that we must focus to see the light. 越是在艰难的时候就越要着眼于光明. From Aristol ...

  10. windows本地远程虚拟机docker中的数据的问题

    关闭各种防火墙 打开宿主机(windows)的cmd,在其中添加通往192.168.1.0/24网络的路由. 通往192.168.1.0/24网络的数据包由172.20.1.12来转发 route a ...