上篇 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. GZIP压缩与解压

    public class GZIP { /** * 字符串的压缩 * * @param str * 待压缩的字符串 * @return 返回压缩后的字符串 * @throws IOException ...

  2. Visual Studio插件开发基础

    Visual Studio插件主要有两种:Add-in 和 VSX(Visual Studio eXtensibility) 两者区别可参考这篇文章:Visual Studio Extensions ...

  3. SQLsever 复制一行内容到本表

    insert into Table (userName,userAge) select userName,userAge from Table where Id=66 这里并不是 insert int ...

  4. leecode.147. 对无头结点链表进行插入排序

    void InsertSort(struct ListNode* L){ struct ListNode *p = L->next,*pre=NULL; struct ListNode *r = ...

  5. Python基础之if判断,while循环,循环嵌套

    if判断 判断的定义 如果条件满足,就做一件事:条件不满足,就做另一件事: 判断语句又被称为分支语句,有判断,才有分支: if判断语句基本语法 if语句格式: if 判断的条件: 条件成立后做的事 . ...

  6. bilibili弹幕爬取

    随便进入一个视频页面,打开开发者工具,清空network空间,进入XHR,刷新抓包. 双击查看弹幕

  7. js深浅拷贝

    作为一枚前段,我们知道对象类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况.通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个情况. 浅拷贝 首先可以通过O ...

  8. jQuery的siblings方法

    在使用siblings方法的时候,发现p标签,选中是没有效果的 解决:在w3c中测试也发现是没有效果的,也没有其他的特殊说明,于是度娘之后发现: siblings()获取的是当前标签元素的所有同辈的标 ...

  9. Core官方DI解析(3)-ServiceCallSite.md

    上一篇说过在整个DI框架中IServiceProviderEngine是核心,但是如果直接看IServiceProviderEngine派生类其实看不出也没什么东西,因为这个类型其实都是调用的其它对象 ...

  10. Linux内存管理专题

    Linux的内存管理涉及到的内容非常庞杂,而且与内核的方方面面耦合在一起,想要理解透彻非常困难. 在开始学习之前进行了一些准备工作<如何展开Linux Memory Management学习?& ...