Spring Security

Spring Security入门

image.png
image.png

认证

验证用户名和密码
image.png

  1. //AuthenticationManager:认证的核心接口
  2. //AuthenticationManagerBuilder:用于构建 AuthenticationManager 对象的工具
  3. //ProviderManager:AuthenticationManager 接口默认实现类
  4. @Override
  5. public void configure(AuthenticationManagerBuilder auth) throws Exception{
  6. //内置认证规则
  7. //auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("1234"))//加盐
  8. //自定义认证规则
  9. //AuthenticationProvider:ProviderManager持有一组 AuthenticationProvider 每个AuthenticationProvider负责一种认证
  10. //采用委托模式:ProviderManager 将认证委托给 AuthenticationProvider
  11. auth.authenticationProvider(new AuthenticationProvider() {
  12. //Authentication: 用于封装认证信息的接口,不同的实现类代表不同类型的认证信息
  13. @Override
  14. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  15. String username = authentication.getName();
  16. String password = (String) authentication.getCredentials();
  17. User user = userService.findUserByName(username);
  18. if(user == null){
  19. throw new UsernameNotFoundException("账号不存在");
  20. }
  21. password = CommunityUtil.md5(password + user.getSalt());
  22. if(!user.getPassword().equals(password)){
  23. throw new BadCredentialsException("密码不正确");
  24. }
  25. return new UsernamePasswordAuthenticationToken(user,user.getPassword(),user.getAuthorities());
  26. }
  27. //当前 AuthenticationProvider 支持哪种类型的认证
  28. @Override
  29. public boolean supports(Class<?> aClass) {
  30. //UsernamePasswordAuthenticationToken: AuthenticationProvider 常用的实现类
  31. return UsernamePasswordAuthenticationToken.class.equals(aClass);
  32. }
  33. });
  34. }

用户认证是我们自己写的逻辑(在LoginTicketInterceptor中),跳过了 Spring Security,那我们就需要把我们自己做的逻辑认证的结果通过SecurityContextHolder存入 SecurityContext,以便于 Spring Security 进行授权

  1. // 构建用户认证的结果,并存入 SecurityContext, 以便于 Spring Security 进行授权
  2. Authentication authentication = new UsernamePasswordAuthenticationToken(
  3. user, user.getPassword(), userService.getAuthorities(user.getId())
  4. );
  5. SecurityContextHolder.setContext(new SecurityContextImpl(authentication));

image.png
重定向
image.png
共同完成一次请求;转发

授权

image.png
image.png

  1. /**
  2. * 授权
  3. * @param http
  4. * @throws Exception
  5. */
  6. @Override
  7. protected void configure(HttpSecurity http) throws Exception {
  8. //授权配置
  9. http.authorizeRequests()
  10. .antMatchers(//添加访问路径
  11. "/user/setting",
  12. "/user/upload",
  13. "/discuss/add",
  14. "/discuss/publish",
  15. "/comment/add/**",
  16. "/letter/**",
  17. "/notice/**",
  18. "/like",
  19. "/follow",
  20. "/unfollow"
  21. )
  22. .hasAnyAuthority(//这些用户拥有访问以上路径的权力
  23. AUTHORITY_USER,
  24. AUTHORITY_ADMIN,
  25. AUTHORITY_MODERATOR
  26. )
  27. .antMatchers(
  28. "/discuss/top",
  29. "/discuss/wonderful"
  30. )
  31. .hasAnyAuthority(
  32. AUTHORITY_MODERATOR
  33. )
  34. .antMatchers(
  35. "/discuss/delete",
  36. "/discuss/delete/",
  37. "/data/**"
  38. )
  39. .hasAnyAuthority(
  40. AUTHORITY_ADMIN
  41. )
  42. .anyRequest().permitAll()
  43. .and().csrf().disable();
  44. // 权限不够时的处理
  45. http.exceptionHandling()
  46. // 1. 未登录时的处理
  47. .authenticationEntryPoint(new AuthenticationEntryPoint() {
  48. @Override
  49. public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
  50. String xRequestedWith = request.getHeader("x-requested-with");
  51. if ("XMLHttpRequest".equals(xRequestedWith)) {
  52. // 异步请求
  53. response.setContentType("application/plain;charset=utf-8");
  54. PrintWriter writer = response.getWriter();
  55. writer.write(CommunityUtil.getJSONString(403, "你还没有登录"));
  56. }
  57. else {
  58. // 普通请求
  59. response.sendRedirect(request.getContextPath() + "/login");
  60. }
  61. }
  62. })
  63. // 2. 权限不够时的处理
  64. .accessDeniedHandler(new AccessDeniedHandler() {
  65. @Override
  66. public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
  67. String xRequestedWith = request.getHeader("x-requested-with");
  68. if ("XMLHttpRequest".equals(xRequestedWith)) {
  69. // 异步请求
  70. response.setContentType("application/plain;charset=utf-8");
  71. PrintWriter writer = response.getWriter();
  72. writer.write(CommunityUtil.getJSONString(403, "你没有访问该功能的权限"));
  73. }
  74. else {
  75. // 普通请求
  76. response.sendRedirect(request.getContextPath() + "/denied");
  77. }
  78. }
  79. });
  80. // Security 底层会默认拦截 /logout 请求,进行退出处理
  81. // 此处赋予它一个根本不存在的退出路径,使得程序能够执行到我们自己编写的退出代码
  82. http.logout().logoutUrl("/securitylogout");
  83. http.headers().frameOptions().sameOrigin();
  84. }

CSRF配置

image.png
用户浏览器在发送表单请求时,访问x网站。x网站窃取cookie中的凭证,伪装用户向服务器发送post请求(修改数据)危害用户账户安全。
解决方式
image.png
引入Spring Security后,用户在请求表单数据时,Security会在表单里默认加一个隐藏的数据 tocken,每次请求tocken都不一样。x网站能窃取到cookie,但是窃取不到tocken, x网站在提交数据时 cookie对,tocken不对,服务器则判断为非法请求,拒绝这样的请求。

Spring Security入门

spring security 的核心功能主要包括:
认证 (你是谁)
授权 (你能干什么)
攻击防护 (防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
image.png
比如,对于username password认证过滤器来说,
会检查是否是一个登录请求;
是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;
如果不满足则放行给下一个。
下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。
注意:绿色的过滤器可以配置是否生效,其他的都不能控制。

原理讲解

1、校验流程图

image.png