背景

前段时间做了一个项目, 因为涉及到权限认证, 所以分别调研了 SpringSecurity 和 Apache Shiro. 最后选择使用了 SpringSecurity + JWT做权限认证, 现在项目已经结束, 总相关笔记. 项目下载地址 jwt-demo

  1. 使用JWT生成token
  2. token存储在数据库中
  3. 使用 application/json 登录
  4. 使用手机号进行登录
  5. URI动态拦截

    配置过程

    添加依赖

  6. 分别添加 SpringSecurity JWT 和 fastjson 依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-security</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>io.jsonwebtoken</groupId>
    7. <artifactId>jjwt</artifactId>
    8. <version>0.9.0</version>
    9. </dependency>
    10. <!--json-->
    11. <dependency>
    12. <groupId>com.alibaba</groupId>
    13. <artifactId>fastjson</artifactId>
    14. <version>1.2.60</version>
    15. </dependency>

    基础准备对象

  • 主要是在用户登录成功handle时使用JWT生成Token返回给客户端.

    基础使用dto

    请求返回基类

    1. @Data
    2. public class BaseReqDto implements Serializable {
    3. private String version;
    4. }
    5. @Data
    6. public class BaseRespDto implements Serializable {
    7. private String resultCode;
    8. private String resultMsg;
    9. private String resultTime;
    10. }

    登录请求返回对象

    1. @Data
    2. public class LoginReqDto {
    3. private String username;
    4. private String token;
    5. }
    6. @Data
    7. public class LoginRespDto extends BaseRespDto {
    8. private String token;
    9. }

    用于验证的用户

    1. package com.liuzhihang.demo.bean;
    2. import org.springframework.security.core.GrantedAuthority;
    3. import org.springframework.security.core.userdetails.UserDetails;
    4. import java.io.Serializable;
    5. import java.util.Collection;
    6. /**
    7. * 用户信息校验验证码
    8. *
    9. * @author liuzhihang
    10. */
    11. public class UserDetailsImpl implements UserDetails, Serializable {
    12. /**
    13. * 用户名
    14. */
    15. private String username;
    16. /**
    17. * 密码
    18. */
    19. private String password;
    20. /**
    21. * 权限集合
    22. */
    23. private Collection<? extends GrantedAuthority> authorities;
    24. @Override
    25. public Collection<? extends GrantedAuthority> getAuthorities() {
    26. return this.authorities;
    27. }
    28. public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
    29. this.authorities = authorities;
    30. }
    31. @Override
    32. public String getPassword() {
    33. return this.password;
    34. }
    35. @Override
    36. public String getUsername() {
    37. return this.username;
    38. }
    39. public void setUsername(String username) {
    40. this.username = username;
    41. }
    42. public void setPassword(String password) {
    43. this.password = password;
    44. }
    45. @Override
    46. public boolean isAccountNonExpired() {
    47. return true;
    48. }
    49. @Override
    50. public boolean isAccountNonLocked() {
    51. return true;
    52. }
    53. @Override
    54. public boolean isCredentialsNonExpired() {
    55. return true;
    56. }
    57. @Override
    58. public boolean isEnabled() {
    59. return true;
    60. }
    61. }

    用户未登录handle

    1. /**
    2. * 用户登录认证, 未登录返回信息
    3. *
    4. * @author liuzhihang
    5. * @date 2019-06-04 13:52
    6. */
    7. @Component
    8. public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    9. private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    10. @Override
    11. public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
    12. response.setContentType("application/json;charset=UTF-8");
    13. LoginRespDto respDto = new LoginRespDto();
    14. respDto.setResultCode("0001");
    15. respDto.setResultMsg("用户未登录");
    16. respDto.setResultTime(LocalDateTime.now().format(FORMATTER));
    17. response.getWriter().write(JSON.toJSONString(respDto));
    18. }
    19. }

    用户登录验证失败handle

    1. /**
    2. * 用户登录认证失败返回的信息
    3. *
    4. * @author liuzhihang
    5. * @date 2019-06-04 13:57
    6. */
    7. @Component
    8. public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {
    9. private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    10. @Override
    11. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
    12. response.setContentType("application/json;charset=UTF-8");
    13. LoginRespDto respDto = new LoginRespDto();
    14. respDto.setResultCode("0001");
    15. respDto.setResultMsg("用户登录认证失败");
    16. respDto.setResultTime(LocalDateTime.now().format(FORMATTER));
    17. response.getWriter().write(JSON.toJSONString(respDto));
    18. }
    19. }

    用户无权访问handle

    1. /**
    2. * 当用户访问无权限页面时, 返回信息
    3. *
    4. * @author liuzhihang
    5. * @date 2019-06-04 14:03
    6. */
    7. @Component
    8. public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    9. private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    10. @Override
    11. public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
    12. response.setContentType("application/json;charset=UTF-8");
    13. LoginRespDto respDto = new LoginRespDto();
    14. respDto.setResultCode("0002");
    15. respDto.setResultMsg("用户无权访问");
    16. respDto.setResultTime(LocalDateTime.now().format(FORMATTER));
    17. response.getWriter().write(JSON.toJSONString(respDto));
    18. }
    19. }

    用户登录成功handle

    1. /**
    2. * 用户登录成功之后的返回信息
    3. *
    4. * @author liuzhihang
    5. * @date 2019-06-04 14:20
    6. */
    7. @Slf4j
    8. @Component
    9. public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
    10. private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    11. @Resource
    12. private JwtTokenUtil jwtTokenUtil;
    13. @Override
    14. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
    15. Authentication authentication) throws IOException {
    16. UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
    17. String jwtToken = jwtTokenUtil.generateToken(userDetails);
    18. // 把生成的token更新到数据库中
    19. // 更新DB操作 ...
    20. response.setContentType("application/json;charset=UTF-8");
    21. LoginRespDto respDto = new LoginRespDto();
    22. respDto.setToken(jwtToken);
    23. respDto.setResultCode("0000");
    24. respDto.setResultMsg("登录成功");
    25. respDto.setResultTime(LocalDateTime.now().format(FORMATTER));
    26. response.getWriter().write(JSON.toJSONString(respDto));
    27. }
    28. }

    JwtTokenUtil

    主要用来生成token和通过token解析对象等操作.

    1. package com.liuzhihang.demo.utils;
    2. import com.liuzhihang.demo.bean.UserDetailsImpl;
    3. import io.jsonwebtoken.Claims;
    4. import io.jsonwebtoken.Jwts;
    5. import io.jsonwebtoken.SignatureAlgorithm;
    6. import org.springframework.security.core.userdetails.UserDetails;
    7. import org.springframework.stereotype.Component;
    8. import java.time.Instant;
    9. import java.util.Date;
    10. /**
    11. * 使用 java-jwt jwt类库
    12. *
    13. * @author liuzhihang
    14. * @date 2019-06-05 09:22
    15. */
    16. @Component
    17. public class JwtTokenUtil {
    18. private static final SignatureAlgorithm SIGN_TYPE = SignatureAlgorithm.HS256;
    19. public static final String SECRET = "jwt-secret";
    20. /**
    21. * JWT超时时间
    22. */
    23. public static final long EXPIRED_TIME = 7 * 24 * 60 * 60 * 1000L;
    24. /**
    25. * claims 为自定义的私有声明, 要放在前面
    26. * <p>
    27. * 生成token
    28. */
    29. public String generateToken(UserDetails userDetails) {
    30. long instantNow = Instant.now().toEpochMilli();
    31. Claims claims = Jwts.claims();
    32. claims.put(Claims.SUBJECT, userDetails.getUsername());
    33. return Jwts.builder().setClaims(claims).setIssuedAt(new Date(instantNow))
    34. .setExpiration(new Date(instantNow + EXPIRED_TIME))
    35. .signWith(SIGN_TYPE, SECRET).compact();
    36. }
    37. /**
    38. * claims 为自定义的私有声明, 要放在前面
    39. * <p>
    40. * 生成token
    41. */
    42. public String generateToken(String userName) {
    43. long instantNow = Instant.now().toEpochMilli();
    44. Claims claims = Jwts.claims();
    45. claims.put(Claims.SUBJECT, userName);
    46. return Jwts.builder().setClaims(claims).setIssuedAt(new Date(instantNow))
    47. .setExpiration(new Date(instantNow + EXPIRED_TIME))
    48. .signWith(SIGN_TYPE, SECRET).compact();
    49. }
    50. /**
    51. * 将token解析, 映射为 UserDetails
    52. *
    53. * @param jwtToken
    54. * @return
    55. */
    56. public UserDetails getUserDetailsFromToken(String jwtToken) {
    57. Claims claimsFromToken = getClaimsFromToken(jwtToken);
    58. String userName = claimsFromToken.get(Claims.SUBJECT, String.class);
    59. UserDetailsImpl userDetails = new UserDetailsImpl();
    60. userDetails.setUsername(userName);
    61. return userDetails;
    62. }
    63. /**
    64. * 验证token
    65. */
    66. public Boolean validateToken(String token, UserDetails userDetails) {
    67. UserDetailsImpl user = (UserDetailsImpl) userDetails;
    68. String username = getPhoneNoFromToken(token);
    69. return (username.equals(user.getUsername()) && !isTokenExpired(token));
    70. }
    71. /**
    72. * 刷新令牌
    73. *
    74. * @param token 原令牌
    75. * @return 新令牌
    76. */
    77. public String refreshToken(String token) {
    78. String refreshedToken;
    79. try {
    80. Claims claims = getClaimsFromToken(token);
    81. long instantNow = Instant.now().toEpochMilli();
    82. refreshedToken = Jwts.builder().setClaims(claims).setIssuedAt(new Date(instantNow))
    83. .setExpiration(new Date(instantNow + EXPIRED_TIME))
    84. .signWith(SIGN_TYPE, SECRET).compact();
    85. } catch (Exception e) {
    86. refreshedToken = null;
    87. }
    88. return refreshedToken;
    89. }
    90. /**
    91. * 获取token是否过期
    92. */
    93. public Boolean isTokenExpired(String token) {
    94. Date expiration = getExpirationDateFromToken(token);
    95. return expiration.before(new Date());
    96. }
    97. /**
    98. * 根据token获取username
    99. */
    100. public String getPhoneNoFromToken(String token) {
    101. return getClaimsFromToken(token).getSubject();
    102. }
    103. /**
    104. * 获取token的过期时间
    105. */
    106. public Date getExpirationDateFromToken(String token) {
    107. return getClaimsFromToken(token).getExpiration();
    108. }
    109. /**
    110. * 解析JWT
    111. */
    112. private Claims getClaimsFromToken(String token) {
    113. return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
    114. }
    115. }

    WebSecurityConfig 核心配置

    1. package com.liuzhihang.demo.config;
    2. import com.liuzhihang.demo.filter.CustomizeAuthenticationFilter;
    3. import com.liuzhihang.demo.filter.JwtPerTokenFilter;
    4. import com.liuzhihang.demo.service.UserDetailServiceImpl;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.context.annotation.Bean;
    7. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    8. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    9. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    10. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    11. import org.springframework.security.config.http.SessionCreationPolicy;
    12. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    13. import org.springframework.security.crypto.password.PasswordEncoder;
    14. import org.springframework.security.web.AuthenticationEntryPoint;
    15. import org.springframework.security.web.access.AccessDeniedHandler;
    16. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    17. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    18. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    19. import javax.annotation.Resource;
    20. /**
    21. * @author liuzhihang
    22. * @date 2019-06-03 14:25
    23. */
    24. @EnableWebSecurity
    25. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    26. @Autowired
    27. private UserDetailServiceImpl userDetailServiceImpl;
    28. @Resource
    29. private JwtPerTokenFilter jwtPerTokenFilter;
    30. @Resource(name = "authenticationEntryPointImpl")
    31. private AuthenticationEntryPoint authenticationEntryPoint;
    32. @Resource(name = "authenticationSuccessHandlerImpl")
    33. private AuthenticationSuccessHandler authenticationSuccessHandler;
    34. @Resource(name = "authenticationFailureHandlerImpl")
    35. private AuthenticationFailureHandler authenticationFailureHandler;
    36. @Resource(name = "accessDeniedHandlerImpl")
    37. private AccessDeniedHandler accessDeniedHandler;
    38. /**
    39. * 创建用于认证授权的用户
    40. *
    41. * @param auth
    42. * @throws Exception
    43. */
    44. @Autowired
    45. public void configureUserInfo(AuthenticationManagerBuilder auth) throws Exception {
    46. // 放入自己的认证授权用户, 内部逻辑需要自己实现
    47. // UserDetailServiceImpl implements UserDetailsService
    48. auth.userDetailsService(userDetailServiceImpl);
    49. }
    50. @Override
    51. protected void configure(HttpSecurity http) throws Exception {
    52. http
    53. // 使用JWT, 关闭session
    54. .csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    55. .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint)
    56. // 登录的权限, 成功返回信息, 失败返回信息
    57. .and().formLogin().permitAll()
    58. .loginProcessingUrl("/login")
    59. // 配置url 权限 antMatchers: 匹配url 权限
    60. .and().authorizeRequests()
    61. .antMatchers("/login", "/getVersion")
    62. .permitAll()
    63. // 其他需要登录才能访问
    64. .anyRequest().access("@dynamicAuthorityService.hasPermission(request,authentication)")
    65. // 访问无权限 location 时
    66. .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
    67. // 自定义过滤
    68. .and().addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
    69. .addFilterBefore(jwtPerTokenFilter, UsernamePasswordAuthenticationFilter.class)
    70. .headers().cacheControl();
    71. }
    72. /**
    73. * 密码加密器
    74. */
    75. @Bean
    76. public PasswordEncoder passwordEncoder() {
    77. /**
    78. * BCryptPasswordEncoder:相同的密码明文每次生成的密文都不同,安全性更高
    79. */
    80. return new BCryptPasswordEncoder();
    81. }
    82. @Bean
    83. CustomizeAuthenticationFilter customAuthenticationFilter() throws Exception {
    84. CustomizeAuthenticationFilter filter = new CustomizeAuthenticationFilter();
    85. filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
    86. filter.setAuthenticationFailureHandler(authenticationFailureHandler);
    87. filter.setAuthenticationManager(authenticationManagerBean());
    88. return filter;
    89. }
    90. }

    登录校验过程

    {% mermaid %}
    graph TD;
    A(请求登录) —> B(CustomizeAuthenticationFilter#attemptAuthentication 解析请求的json);
    B —> C(UserDetailServiceImpl#loadUserByUsername 验证用户名密码);
    C —> D(AuthenticationSuccessHandlerImpl#onAuthenticationSuccess 构建返回参数 包括token);
    D —> E(返回结果)
    {% endmermaid %}

    自定义拦截器解析 json 报文

    前端请求登录报文类型为 application/json 需要后端增加拦截器, 对登录请求报文进行解析

    1. package com.liuzhihang.demo.filter;
    2. import com.alibaba.fastjson.JSON;
    3. import com.alibaba.fastjson.JSONException;
    4. import com.alibaba.fastjson.JSONObject;
    5. import lombok.extern.slf4j.Slf4j;
    6. import org.springframework.http.MediaType;
    7. import org.springframework.security.authentication.AuthenticationServiceException;
    8. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    9. import org.springframework.security.core.Authentication;
    10. import org.springframework.security.core.AuthenticationException;
    11. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    12. import javax.servlet.http.HttpServletRequest;
    13. import javax.servlet.http.HttpServletResponse;
    14. import java.io.BufferedReader;
    15. import java.io.IOException;
    16. /**
    17. *
    18. * 自定义拦截器, 重写UsernamePasswordAuthenticationFilter 从而可以处理 application/json 中的json请求报文
    19. *
    20. * @author liuzhihang
    21. * @date 2019-06-12 19:04
    22. */
    23. @Slf4j
    24. public class CustomizeAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    25. @Override
    26. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    27. throws AuthenticationException {
    28. // attempt Authentication when Content-Type is json
    29. if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)
    30. || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
    31. try {
    32. BufferedReader br = request.getReader();
    33. String str;
    34. StringBuilder jsonStr = new StringBuilder();
    35. while ((str = br.readLine()) != null) {
    36. jsonStr.append(str);
    37. }
    38. log.info("本次登录请求参数:{}", jsonStr);
    39. JSONObject jsonObject = JSON.parseObject(jsonStr.toString());
    40. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
    41. jsonObject.getString("username"), jsonObject.getString("password"));
    42. setDetails(request, authRequest);
    43. return this.getAuthenticationManager().authenticate(authRequest);
    44. } catch (IOException e) {
    45. log.info("用户登录, 请求参数 不正确");
    46. throw new AuthenticationServiceException("获取报文请求参数失败");
    47. } catch (JSONException e) {
    48. log.info("用户登录, 请求报文格式 不正确");
    49. throw new AuthenticationServiceException("请求报文, 转换Json失败");
    50. }
    51. } else {
    52. log.error("用户登录, contentType 不正确");
    53. throw new AuthenticationServiceException(
    54. "请求 contentType 不正确, 请使用 application/json;charset=UTF-8 或者 application/json;");
    55. }
    56. }
    57. }

    用户认证模块

  • 根据获取到的username从数据库中查询到密码, 将用户名密码赋值给UserDetails对象, 返回其他的框架会进行校验

  • 这边使用中是使用的手机号+验证码登录, 所以 上面json解析的也是 phoneNo+verificationCode
  • 在这块 username仅仅代指登录名, 可以是手机号可以是别的.
  • 这边使用中验证码是从redis中获取的. 获取不到返回失败, 获取到和传递的不一致也算失败.
    1. package com.liuzhihang.demo.service;
    2. import com.liuzhihang.demo.bean.UserDetailsImpl;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.security.core.userdetails.UserDetails;
    5. import org.springframework.security.core.userdetails.UserDetailsService;
    6. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    7. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    8. import org.springframework.stereotype.Component;
    9. /**
    10. * @author liuzhihang
    11. */
    12. @Slf4j
    13. @Component("userDetailServiceImpl")
    14. public class UserDetailServiceImpl implements UserDetailsService {
    15. /**
    16. * 用来验证登录名是否有权限进行登录
    17. *
    18. * 可以通过数据库进行校验 也可以通过redis 等等
    19. *
    20. * @param username
    21. * @return
    22. * @throws UsernameNotFoundException
    23. */
    24. @Override
    25. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    26. UserDetailsImpl userDetailsImpl = new UserDetailsImpl();
    27. userDetailsImpl.setUsername("liuzhihang");
    28. userDetailsImpl.setPassword(new BCryptPasswordEncoder().encode("123456789"));
    29. return userDetailsImpl;
    30. }
    31. }

    请求校验过程

    {% mermaid %}
    graph TD;
    A(请求接口) —> B(JwtPerTokenFilter#doFilterInternal 验证Header中的token);
    B —> C(DynamicAuthorityService#hasPermission 验证有没有请求url权限);
    C —> D(处理逻辑);
    D —> E(返回结果)
    {% endmermaid %}

    JWTToken拦截器

    主要是拦截请求, 验证Header中的token是否正确
    1. package com.liuzhihang.demo.filter;
    2. import com.liuzhihang.demo.utils.JwtTokenUtil;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    6. import org.springframework.security.core.context.SecurityContextHolder;
    7. import org.springframework.security.core.userdetails.UserDetails;
    8. import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    9. import org.springframework.stereotype.Component;
    10. import org.springframework.web.filter.OncePerRequestFilter;
    11. import javax.servlet.FilterChain;
    12. import javax.servlet.ServletException;
    13. import javax.servlet.http.HttpServletRequest;
    14. import javax.servlet.http.HttpServletResponse;
    15. import java.io.IOException;
    16. /**
    17. * @author liuzhihang
    18. * @date 2019-06-05 09:09
    19. */
    20. @Slf4j
    21. @Component
    22. public class JwtPerTokenFilter extends OncePerRequestFilter {
    23. @Autowired
    24. private JwtTokenUtil jwtTokenUtil;
    25. /**
    26. * 存放Token的Header Key
    27. */
    28. private static final String HEADER_STRING = "token";
    29. @Override
    30. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    31. throws ServletException, IOException {
    32. String token = request.getHeader(HEADER_STRING);
    33. if (null != token && !jwtTokenUtil.isTokenExpired(token)) {
    34. UserDetails userDetails = jwtTokenUtil.getUserDetailsFromToken(token);
    35. String username = userDetails.getUsername();
    36. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
    37. // 通过 username 查询数据库 获取token 然后和库中token作比较
    38. if (username.equals("liuzhihang")) {
    39. UsernamePasswordAuthenticationToken authentication =
    40. new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    41. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
    42. SecurityContextHolder.getContext().setAuthentication(authentication);
    43. }
    44. }
    45. }
    46. filterChain.doFilter(request, response);
    47. }
    48. }

    URI动态校验

    1. package com.liuzhihang.demo.service;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    4. import org.springframework.security.core.Authentication;
    5. import org.springframework.security.core.userdetails.UserDetails;
    6. import org.springframework.stereotype.Component;
    7. import javax.servlet.http.HttpServletRequest;
    8. import java.util.HashSet;
    9. import java.util.Set;
    10. /**
    11. * 动态权限认证
    12. *
    13. * @author liuzhihang
    14. * @date 2019-06-25 15:51
    15. */
    16. @Slf4j
    17. @Component(value = "dynamicAuthorityService")
    18. public class DynamicAuthorityService {
    19. public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
    20. try {
    21. Object principal = authentication.getPrincipal();
    22. if (principal instanceof UserDetails && authentication instanceof UsernamePasswordAuthenticationToken) {
    23. // 本次请求的uri
    24. String uri = request.getRequestURI();
    25. // 获取当前用户
    26. UserDetails userDetails = (UserDetails) principal;
    27. String username = userDetails.getUsername();
    28. log.info("本次用户请求认证, username:{}, uri:{}", username, uri);
    29. // 从数据库取逻辑
    30. if (username.equals("liuzhihang")){
    31. Set<String> set = new HashSet<>();
    32. set.add("/homeInfo");
    33. set.add("/getAllUser");
    34. set.add("/editUserInfo");
    35. if (set.contains(uri)) {
    36. return true;
    37. }
    38. }
    39. }
    40. } catch (Exception e) {
    41. log.error("用户请求登录, uri:{} error", request.getRequestURI(), e);
    42. return false;
    43. }
    44. return false;
    45. }
    46. }

    测试

    脚本在 httpclient脚本
    1. POST localhost:8080/login
    2. Content-Type: application/json
    3. {
    4. "username": "liuzhihang",
    5. "password": "123456789"
    6. }
    7. ### 请求接口脚本
    8. POST localhost:8080/homeInfo
    9. Content-Type: application/json
    10. token: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsaXV6aGloYW5nIiwiaWF0IjoxNTY5MDI1NjY4LCJleHAiOjE1Njk2MzA0Njh9.Kot_uLnwtcq-t5o4x3V-xBnpf-mKEi7OV2eAfgMCKLk
    11. ###
    返回:
    1. {
    2. "resultCode": "0000",
    3. "resultMsg": "登录成功",
    4. "resultTime": "20190920191038",
    5. "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsaXV6aGloYW5nIiwiaWF0IjoxNTY4OTc3ODM4LCJleHAiOjE1Njk1ODI2Mzh9.MAS9VkFdCF3agkCgTtc0VzPMFjY42vFyIvAEzkSeAfs"
    6. }

    参考

    前后端分离 SpringBoot + SpringSecurity + JWT + RBAC 实现用户无状态请求验证