上篇 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. C#零基础入门-3-第一个控制台程序

    打开VS2017 文件 新建 项目 模板选择Visual C# Windows 控制台应用程序 快速写入Console.WriteLine 输入cw,然后快速按tab键两次即可.

  2. 为什么CynosDB叫真正的云原生数据库?

    本文由腾讯云数据库发表 注:本文摘自2018年11月22日腾讯云数据库CynosDB新品发布会的演讲实录.随着互联网信息的发展,大家也对云这个词汇也不是特别陌生了,作为全球首选的云服务厂商之一的腾讯云 ...

  3. 【原】Java学习笔记032 - 多线程

    package cn.temptation; public class Sample01 { public static void main(String[] args) { /* * [进程]:正在 ...

  4. Linux(三)——Unix&Linux 的基础命令

    Linux(三)--Unix&Linux 的基础命令 快捷键 Ctl-A 光标移动到行首 Ctl-C 终止命令 Ctl-D 注销登录 Ctl-E 光标移动到行尾 Ctl-U 删除光标到行首的所 ...

  5. Maven settings.xml

    <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...

  6. springboot 应用中静态资源下载

    一. 场景介绍 Excel模板静态资源在,应用中的static文件夹中,文件名称包含中文; 需求:页面直接访问下载Excel模板. 二.目录结构 三.后台代码 @GetMapping("/d ...

  7. Neutron Vlan Network 学习

    vlan network 是带 tag 的网络,是实际应用最广泛的网络类型.    下图是 vlan100 网络的示例.   1. 三个 instance 通过 TAP 设备连接到名为 brqXXXX ...

  8. Smart Indenter for VBE(64bits smart indent addin for VBA Editor),VBA开发必备的智能排版工具。

    原始出处:www.cnblogs.com/Charltsing/p/SmartIndenter64.html 作者QQ: 564955427 最近更换电脑,改用64位office做开发.VBA代码美化 ...

  9. Python类和对象

    目录 类与对象 其他方法 继承 经典类和新式类 派生 组合 接口.归一化设计与抽象类 继承实现的原理 子类中调用父类的方法 指名道姓 super()方法 多态与多态性 封装 单下划线开头 双下划线开头 ...

  10. python使用装饰器对文件进行读写操作'及遍历文件目录

    '''使用装饰器对文件进行读写操作''' # def check_permission(func): # '''演示嵌套函数定义及使用''' # def wrapper(*args,**kwargs) ...