上篇 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. (爬虫)requests库

    一.requests库简介 urllib库和request库的作用一样,都是服务器发起请求数据,但是requests库比urllib库用起来更方便,它的接口更简单,选用哪种库看自己. 如果没有安装过这 ...

  2. MyDAL - is null && is not null 条件 使用

    索引: 目录索引 一.API 列表 C# 代码中 instance.property == null 生成 SQL 对应的 is null : 如:.Queryer<Agent>() .. ...

  3. html元素禁用disable or enable

    场景说明 ajax提交数据,防止收到服务端相应前用户重复点击. 1.用户点击按钮,禁用当前按钮,发起ajax请求. 2.收到ajax请求,还原当前按钮. html解决方案 参考地址:http://ww ...

  4. pyspider 初次使用

    一 安装 pip install pyspider 请安装PhantomJS:http://phantomjs.org/build.html 二 检验是否启动成功 cmd中输入: pyspider 安 ...

  5. HTML基础-------HTML标签(3)

    HTML标签(3) 表格 作用:制作一个表格 属性: 标签;table>tr>td(或者th) 语义; table:一个表格 tr:一行 td:一个单元格 th:单元格的表头 captio ...

  6. P1551 亲戚题解

    标准并查集板子题 没啥好说的,分明是白书上的(除了输入方式外一点都没改动) #include<cstdio> #include<iostream> using namespac ...

  7. Neutron:浮动ip

    如果需要从外网直接访问 instance,则可以利用 floating IP.   下面是关于 floating IP 必须知道的事实: 1. floating IP 提供静态 NAT 功能,建立外网 ...

  8. ubuntu下安装Visual Studio Code

    环境准备 先安装一般umake没有问题 sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make sudo apt-get update sudo ...

  9. MySQL工作原理

    Mysql是由SQL接口,解析器,优化器,缓存,存储引擎组成的.  mysql原理图各个组件说明: 1. connectors 与其他编程语言中的sql 语句进行交互,如php.java等. 2. M ...

  10. Appuim的安装步骤

    1.下载Appium Desktop并安装 下载地址:https://github.com/appium/appium-desktop/releases 我下载的版本为:appium-desktop- ...