security 认证流程

分为两个部分,认证和鉴权
当一个请求到来时,我们需要观察其是否是已经认证的用户,主要是查看浏览器的cookie和remerber me 数据库。如果不是认证用户转到登录,这时候我们就进入了登录的逻辑里。
如果是认证用户,我们就进入鉴权的逻辑。
具体的逻辑我们可以看下图
image.png
说起JWT,我们应该来谈一谈基于token的认证和传统的session认证的区别。

什么是 JWT — JSON WEB TOKEN

传统的session认证

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *。

实现

因为secrity他的默认是基于session和cooki只上的,所以我们需要对其的一些逻辑进行更改。

  1. 取消csrf的防护(crsf是基于cookie的攻击,所以可以关掉防护)

    1. .csrf().disable()
  2. 关掉session

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3. /*AccessDecisionManager accessDecisionManager = (AccessDecisionManager) ioc.getBean("dynamicAccessDecisionManager");*/
  4. http.authorizeRequests()
  5. .antMatchers(excludeUrls).permitAll()
  6. .antMatchers("/admin/**").hasRole("admin")
  7. .antMatchers("/user/**").hasRole("user")
  8. .anyRequest().authenticated()
  9. .and()
  10. .csrf().disable() // 禁用 Spring Security 自带的跨域处理
  11. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  12. .and()
  13. .addFilterAfter(new JwtFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
  14. // 定制我们自己的 session 策略:调整为让 Spring Security 不创建和使用 session;
  15. //.accessDecisionManager(accessDecisionManager) //根据voter配置动态权限
  16. }

具体更改流程

jwt只需要对其认证原理进行更改,每一次带token的链接,我们都需要为其加上一个认证的authentication,并且验证这个authentication。

得到authenication

通过过滤器从token中得到authenication对象

验证authenication

重新配置authenicationProvider

代码

pom

  1. <dependency>
  2. <groupId>com.auth0</groupId>
  3. <artifactId>java-jwt</artifactId>
  4. <version>3.4.0</version>
  5. </dependency>
  6. <!-->序列化<-->
  7. <dependency>
  8. <groupId>com.alibaba</groupId>
  9. <artifactId>fastjson</artifactId>
  10. <version>1.2.79</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.security</groupId>
  14. <artifactId>spring-security-test</artifactId>
  15. <scope>test</scope>
  16. </dependency>

token util

  1. package com.example.springbootsecurityjwtdemo.security;
  2. import com.alibaba.fastjson.JSON;
  3. import com.auth0.jwt.JWT;
  4. import com.auth0.jwt.JWTVerifier;
  5. import com.auth0.jwt.algorithms.Algorithm;
  6. import com.auth0.jwt.interfaces.Claim;
  7. import com.auth0.jwt.interfaces.DecodedJWT;
  8. import org.springframework.security.core.GrantedAuthority;
  9. import org.springframework.stereotype.Component;
  10. import java.util.Date;
  11. import java.util.HashMap;
  12. import java.util.List;
  13. import java.util.Map;
  14. public class TokenUtil {
  15. // 过期时间是七天
  16. private static final long EXPIRE_TIME= 60*60*24*7;
  17. private static final String ISSER = "auth0";
  18. private static final String TOKEN_SECRET="token123"; //密钥盐
  19. public final static JWTVerifier VERIFIER = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer(ISSER).build();
  20. /**
  21. * 签名生成
  22. * @return
  23. */
  24. public static String sign(String name, List<String> roles){
  25. String token = null;
  26. try {
  27. Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
  28. token = JWT.create()
  29. .withIssuer(ISSER)
  30. .withClaim("username", name)
  31. .withClaim("roles", JSON.toJSONString(roles))
  32. .withExpiresAt(expiresAt)
  33. // 使用了HMAC256加密算法。
  34. .sign(Algorithm.HMAC256(TOKEN_SECRET));
  35. } catch (Exception e){
  36. e.printStackTrace();
  37. }
  38. return token;
  39. }
  40. /**
  41. * 签名验证
  42. * @param token
  43. * @return
  44. */
  45. public static boolean verify(String token){
  46. try {
  47. VERIFIER.verify(token);
  48. return true;
  49. } catch (Exception e){
  50. return false;
  51. }
  52. }
  53. /**
  54. * 得到token存的值
  55. * @param token
  56. * @return
  57. */
  58. public static Map<String,Claim> getClaimsFromToken(String token){
  59. try {
  60. DecodedJWT jwt = VERIFIER.verify(token);
  61. return jwt.getClaims();
  62. } catch (Exception e){
  63. return null;
  64. }
  65. }
  66. }

过滤器

  1. import com.alibaba.fastjson.JSON;
  2. import com.auth0.jwt.interfaces.Claim;
  3. import com.example.springbootsecurityjwtdemo.exception.BusinessException;
  4. import com.example.springbootsecurityjwtdemo.result.ResponseMag;
  5. import org.springframework.security.access.SecurityConfig;
  6. import org.springframework.security.authentication.AuthenticationManager;
  7. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  8. import org.springframework.security.core.GrantedAuthority;
  9. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  10. import org.springframework.security.core.context.SecurityContextHolder;
  11. import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
  12. import javax.servlet.*;
  13. import javax.servlet.http.HttpServletRequest;
  14. import javax.servlet.http.HttpServletResponse;
  15. import java.io.IOException;
  16. import java.util.ArrayList;
  17. import java.util.List;
  18. import java.util.Map;
  19. public class JwtFilter extends BasicAuthenticationFilter {
  20. public JwtFilter(AuthenticationManager authenticationManager) {
  21. super(authenticationManager);
  22. }
  23. @Override
  24. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
  25. String token = request.getHeader("token");
  26. System.out.println(token);
  27. if(token==null|| "".equals(token)){
  28. chain.doFilter(request, response);
  29. return;
  30. }
  31. Map<String, Claim> claimMap = TokenUtil.getClaimsFromToken(token);
  32. if(claimMap==null){
  33. changeToExectionController(request,response, new BusinessException(ResponseMag.TokenError));
  34. }
  35. String username = claimMap.get("username").asString();
  36. List<String> roles = JSON.parseArray(claimMap.get("roles").asString(),String.class);
  37. List<GrantedAuthority> authorities = new ArrayList<>();
  38. for(String s:roles){
  39. authorities.add(()->s);
  40. }
  41. System.out.println(claimMap.get("roles").asString());
  42. UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username,null,authorities);
  43. SecurityContextHolder.getContext().setAuthentication(authentication);
  44. getAuthenticationManager().authenticate(authentication);
  45. chain.doFilter(request, response);
  46. }
  47. private void changeToExectionController(HttpServletRequest request, HttpServletResponse response,Exception e) throws ServletException, IOException {
  48. // 异常捕获、发送到UnsupportedJwtException
  49. request.setAttribute("Exception", e);
  50. // 将异常分发到UnsupportedJwtException控制器
  51. request.getRequestDispatcher("/Exception").forward(request, response);
  52. }
  53. }

provider

  1. package com.example.springbootsecurityjwtdemo.security;
  2. import java.util.ArrayList;
  3. import org.springframework.security.authentication.AuthenticationProvider;
  4. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  5. import org.springframework.security.core.Authentication;
  6. import org.springframework.security.core.AuthenticationException;
  7. public class CustomAuthenticationProvider implements AuthenticationProvider {
  8. @Override
  9. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  10. System.out.println("hello");
  11. return authentication;
  12. }
  13. @Override
  14. public boolean supports(Class<?> aClass) {
  15. return true;
  16. }
  17. }

配置类

  1. package com.example.springbootsecurityjwtdemo.security;
  2. import com.example.springbootsecurityjwtdemo.mapper.RolePermissionMapper;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.security.access.AccessDecisionManager;
  8. import org.springframework.security.access.AccessDecisionVoter;
  9. import org.springframework.security.access.ConfigAttribute;
  10. import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
  11. import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
  12. import org.springframework.security.authentication.AuthenticationManager;
  13. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  14. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  15. import org.springframework.security.config.annotation.web.builders.WebSecurity;
  16. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  17. import org.springframework.security.config.http.SessionCreationPolicy;
  18. import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
  19. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  20. import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
  21. import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
  22. import javax.sql.DataSource;
  23. @Configuration
  24. public class MySecurityConfig extends WebSecurityConfigurerAdapter {
  25. @Autowired
  26. UserServiceImpl userServiceImpl;
  27. @Override
  28. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  29. auth.authenticationProvider(new CustomAuthenticationProvider());
  30. }
  31. @Autowired
  32. ApplicationContext ioc;
  33. public final String[] excludeUrls = {"/Exception","/login","/register","/doLogin","/test/hello","/swagger**/**","/webjars/**","/v3/**"};
  34. @Override
  35. public void configure(WebSecurity web) throws Exception {
  36. web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
  37. web.securityInterceptor((FilterSecurityInterceptor) ioc.getBean("filterSecurityInterceptor"));
  38. }
  39. @Override
  40. @Bean
  41. public AuthenticationManager authenticationManagerBean() throws Exception {
  42. return super.authenticationManagerBean();
  43. }
  44. @Override
  45. protected void configure(HttpSecurity http) throws Exception {
  46. /*AccessDecisionManager accessDecisionManager = (AccessDecisionManager) ioc.getBean("dynamicAccessDecisionManager");*/
  47. http.authorizeRequests()
  48. .antMatchers(excludeUrls).permitAll()
  49. .antMatchers("/admin/**").hasRole("admin")
  50. .antMatchers("/user/**").hasRole("user")
  51. .anyRequest().authenticated()
  52. .and()
  53. .csrf().disable() // 禁用 Spring Security 自带的跨域处理
  54. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  55. .and()
  56. .addFilterAfter(new JwtFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
  57. // 定制我们自己的 session 策略:调整为让 Spring Security 不创建和使用 session;
  58. //.accessDecisionManager(accessDecisionManager) //根据voter配置动态权限
  59. }
  60. @Bean
  61. RoleHierarchy roleHierarchy() {
  62. RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
  63. hierarchy.setHierarchy("ROLE_admin > ROLE_user");
  64. return hierarchy;
  65. }
  66. }

gitee

https://gitee.com/jefferyeven/jwt_security_demo.git