官网链接

认证原理分析

:::tips Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。 当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此 类,下图是Spring Security过虑器链结构图 ::: image.png :::tips FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理。 :::

使用步骤:

:::tips
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> :::

自定义Security认证过滤器

  1. import com.fasterxml.jackson.databind.ObjectMapper;
  2. import org.springframework.http.MediaType;
  3. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.core.AuthenticationException;
  6. import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
  7. import javax.servlet.FilterChain;
  8. import javax.servlet.ServletException;
  9. import javax.servlet.ServletInputStream;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.awt.*;
  13. import java.io.IOException;
  14. import java.util.HashMap;
  15. /**
  16. * @author by itheima
  17. * @Date 2022/1/21
  18. * @Description
  19. */
  20. public class MyUserNamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  21. /**
  22. * 设置构造,传入自定义登录url地址
  23. * @param defaultFilterProcessesUrl
  24. */
  25. public MyUserNamePasswordAuthenticationFilter(String defaultFilterProcessesUrl) {
  26. super(defaultFilterProcessesUrl);
  27. }
  28. @Override
  29. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
  30. //从post请求流中获取登录信息
  31. String username=null;
  32. String password=null;
  33. try {
  34. //判断 是否是ajax登录
  35. if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
  36. ServletInputStream in = request.getInputStream();
  37. HashMap<String,String> map = new ObjectMapper().readValue(in, HashMap.class);
  38. username=map.get("username");
  39. password=map.get("password");
  40. }
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. }
  44. //生成认证token
  45. UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
  46. //交给认证管理器取认证token
  47. return this.getAuthenticationManager().authenticate(token);
  48. }
  49. /**
  50. * 认证成功处理方法
  51. * @param request
  52. * @param response
  53. * @param chain
  54. * @param authResult
  55. * @throws IOException
  56. * @throws ServletException
  57. */
  58. @Override
  59. protected void successfulAuthentication(HttpServletRequest request,
  60. HttpServletResponse response,
  61. FilterChain chain,
  62. Authentication authResult) throws IOException, ServletException {
  63. //认证主体信息
  64. UserDetails principal = (UserDetails) authResult.getPrincipal();
  65. //组装响应前端的信息
  66. String username = principal.getUsername();
  67. String password = principal.getPassword();
  68. Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();
  69. //构建JwtToken
  70. String token = JwtTokenUtil.createToken(username, new Gson().toJson(authorities));
  71. HashMap<String, String> info = new HashMap<>();
  72. info.put("name",username);
  73. info.put("token",token);
  74. //设置响应格式
  75. response.setContentType(MediaType.APPLICATION_JSON_VALUE);
  76. response.getWriter().write(new Gson().toJson(info));
  77. }
  78. /**
  79. * 认证失败处理方法
  80. * @param request
  81. * @param response
  82. * @param failed
  83. * @throws IOException
  84. * @throws ServletException
  85. */
  86. @Override
  87. protected void unsuccessfulAuthentication(HttpServletRequest request,
  88. HttpServletResponse response,
  89. AuthenticationException failed) throws IOException, ServletException {
  90. String returnData="";
  91. // 账号过期
  92. if (failed instanceof AccountExpiredException) {
  93. returnData="账号过期";
  94. }
  95. // 密码错误
  96. else if (failed instanceof BadCredentialsException) {
  97. returnData="密码错误";
  98. }
  99. // 密码过期
  100. else if (failed instanceof CredentialsExpiredException) {
  101. returnData="密码过期";
  102. }
  103. // 账号不可用
  104. else if (failed instanceof DisabledException) {
  105. returnData="账号不可用";
  106. }
  107. //账号锁定
  108. else if (failed instanceof LockedException) {
  109. returnData="账号锁定";
  110. }
  111. // 用户不存在
  112. else if (failed instanceof InternalAuthenticationServiceException) {
  113. returnData="用户不存在";
  114. }
  115. // 其他错误
  116. else{
  117. returnData="未知异常";
  118. }
  119. // 处理编码方式 防止中文乱码
  120. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  121. // 将反馈塞到HttpServletResponse中返回给前台
  122. R result = R.error(returnData);
  123. response.getWriter().write(new Gson().toJson(result));
  124. }
  125. }

定义获取用户详情服务bean

  1. import com.itheima.entity.TbUser;
  2. import com.itheima.mapper.TbUserMapper;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.security.core.GrantedAuthority;
  5. import org.springframework.security.core.authority.AuthorityUtils;
  6. import org.springframework.security.core.userdetails.User;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.security.core.userdetails.UserDetailsService;
  9. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  10. import org.springframework.stereotype.Service;
  11. import java.util.List;
  12. /**
  13. * @author by itheima
  14. * @Date 2022/5/23
  15. * @Description
  16. */
  17. @Service("userDetailsService")
  18. public class MyUserDetailServiceImpl implements UserDetailsService {
  19. @Autowired
  20. private TbUserMapper tbUserMapper;
  21. /**
  22. * 使用security当用户认证时,会自动将用户的名称注入到该方法中
  23. * 然后我们可以自己写逻辑取加载用户的信息,然后组装成UserDetails认证对象
  24. * @param userName
  25. * @return 用户的基础信息,包含密码和权限集合,security底层会自动比对前端输入的明文密码
  26. * @throws UsernameNotFoundException
  27. */
  28. @Override
  29. public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
  30. //1.根据用户名称获取用户的账户信息
  31. TbUser dbUser=tbUserMapper.findUserInfoByName(userName);
  32. //判断该用户是否存在
  33. if (dbUser==null) {
  34. throw new UsernameNotFoundException("用户名输入错误!");
  35. }
  36. //2.组装UserDetails对象
  37. //获取当前用户对应的权限集合(自动将以逗号间隔的权限字符串封装到权限集合中)
  38. List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(dbUser.getRoles());
  39. /*
  40. 参数1:账户
  41. 参数2:密码
  42. 参数3:权限集合
  43. */
  44. User user = new User(dbUser.getUsername(), dbUser.getPassword(), list);
  45. return user;
  46. }
  47. }

定义SecurityConfig类

  1. /**
  2. * 配置授权策略
  3. * @param http
  4. * @throws Exception
  5. */
  6. @Override
  7. protected void configure(HttpSecurity http) throws Exception {
  8. http
  9. //坑-过滤器要添加在默认过滤器之前,否则,登录失效
  10. .addFilterBefore(myUserNamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
  11. .csrf().disable();
  12. }
  13. @Bean
  14. public MyUserNamePasswordAuthenticationFilter myUserNamePasswordAuthenticationFilter() throws Exception {
  15. //设置默认登录路径
  16. MyUserNamePasswordAuthenticationFilter myUserNamePasswordAuthenticationFilter =
  17. new MyUserNamePasswordAuthenticationFilter("/authentication/form");
  18. myUserNamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
  19. return myUserNamePasswordAuthenticationFilter;
  20. }

自定义权限认证过滤

  1. import com.google.gson.Gson;
  2. import com.itheima.security.utils.JwtTokenUtil;
  3. import org.springframework.http.MediaType;
  4. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  5. import org.springframework.security.core.authority.AuthorityUtils;
  6. import org.springframework.security.core.context.SecurityContextHolder;
  7. import org.springframework.web.filter.OncePerRequestFilter;
  8. import javax.servlet.FilterChain;
  9. import javax.servlet.ServletException;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. import java.util.HashMap;
  14. /**
  15. * @author by itheima
  16. * @Date 2022/1/23
  17. * @Description 权限认证filter
  18. */
  19. public class AuthenticationFilter extends OncePerRequestFilter {
  20. @Override
  21. protected void doFilterInternal(HttpServletRequest request,
  22. HttpServletResponse response,
  23. FilterChain filterChain) throws ServletException, IOException {
  24. //1.从http请求头中获取token
  25. String token = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
  26. if (token==null) {
  27. //用户未登录,则放行,去登录拦截
  28. filterChain.doFilter(request,response);
  29. return;
  30. }
  31. //2.token存在则,安全校验
  32. try {
  33. String username = JwtTokenUtil.getUsername(token);
  34. //获取以逗号间隔的权限拼接字符串
  35. String userRole = JwtTokenUtil.getUserRole(token);
  36. //组装token
  37. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(userRole));
  38. //将生成的token存入上下文
  39. SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  40. //放行资源
  41. filterChain.doFilter(request,response);
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. // throw new RuntimeException("token无效");
  45. response.setContentType(MediaType.APPLICATION_JSON_VALUE);
  46. HashMap<String, String> info = new HashMap<>();
  47. info.put("status","0");
  48. info.put("ex","无效的token凭证");
  49. response.getWriter().write(new Gson().toJson(info));
  50. }
  51. }
  52. }

配置授权拒绝策略

image.png

自定义访问拒绝处理器

  1. import com.google.gson.Gson;
  2. import com.itheima.stock.vo.resp.R;
  3. import com.itheima.stock.vo.resp.ResponseCode;
  4. import org.springframework.http.MediaType;
  5. import org.springframework.security.access.AccessDeniedException;
  6. import org.springframework.security.web.access.AccessDeniedHandler;
  7. import javax.servlet.ServletException;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10. import java.io.IOException;
  11. /**
  12. * 认证用户无权限访问资源时处理器
  13. */
  14. public class StockAccessDeniedHandler implements AccessDeniedHandler {
  15. @Override
  16. public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
  17. //设置响应数据格式
  18. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  19. //构建结果
  20. R result = R.error(ResponseCode.NOT_PERMISSION.getCode(),ResponseCode.NOT_PERMISSION.getMessage());
  21. //将对象序列化为json字符串响应前台
  22. response.getWriter().write(new Gson().toJson(result));
  23. }
  24. }

自定义匿名用户拒绝处理器

import com.google.gson.Gson;
import com.itheima.stock.vo.resp.R;
import com.itheima.stock.vo.resp.ResponseCode;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 匿名用户(即未登录时访问资源为匿名访问)无权限处理器
 */
public class AccessAnonymousEntryPoint implements AuthenticationEntryPoint {

    /**
     * 当用户请求了一个受保护的资源,但是用户没有通过认证,那么抛出异常,
     * AuthenticationEntryPoint. Commence(..)就会被调用。
     * @param request
     * @param response
     * @param authException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        //设置响应数据格式
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        //构建结果
        R result = R.error(ResponseCode.NOT_PERMISSION.getCode(),ResponseCode.NOT_PERMISSION.getMessage());
        //将对象序列化为json字符串响应前台
        response.getWriter().write(new Gson().toJson(result));
    }
}

整体流程小结

image.png
image.png