• Spring Security笔记


    这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

    Spring Security简介

    Spring Security 是一种高度自定义的安全框架,利用(基于)SpringIOC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作

    核心功能:认证和授权

    Spring Security 认证流程

    SpringSecurity认证执行流程.png

    Spring Security 项目搭建

    导入依赖

    Spring Security已经被Spring boot进行集成,使用时直接引入启动器即可

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-securityartifactId>
    4. dependency>
    5. 复制代码

    访问页面

    导入spring-boot-starter-security启动器后,Spring Security已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。

    在浏览器输入:http://localhost:8080/ 进入Spring Security内置登录页面

    用户名: user

    密码:项目启动,打印在控制台中

    自定义用户名和密码

    修改application.yml 文件

    1. # 静态用户,一般只在内部网络认证中使用,如:内部服务器1,访问服务器2
    2. spring:
    3. security:
    4. user:
    5. name: test # 通过配置文件,设置静态用户名
    6. password: test # 配置文件,设置静态登录密码
    7. 复制代码

    UserDetailsService详解

    什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现UserDetailsService接口

    1. @Component
    2. public class UserSecurity implements UserDetailsService {
    3. @Autowired
    4. private UserService userService;
    5. @Override
    6. public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    7. User user = userService.login(userName);
    8. System.out.println(user);
    9. if (null==user){
    10. throw new UsernameNotFoundException("用户名错误");
    11. }
    12. org.springframework.security.core.userdetails.User result =
    13. new org.springframework.security.core.userdetails.User(
    14. userName,user.getPassword(), AuthorityUtils.createAuthorityList()
    15. );
    16. return result;
    17. }
    18. }
    19. 复制代码

    PasswordEncoder密码解析器详解

    PasswordEncoder

    PasswordEncoder 是SpringSecurity 的密码解析器,用户密码校验、加密 。 自定义登录逻辑时要求必须给容器注入PaswordEncoder的bean对象

    SpringSecurity 定义了很多实现接口PasswordEncoder 满足我们密码加密、密码校验 使用需求

    自定义密码解析器

    1. 编写类,实现PasswordEncoder 接口
    1. /**
    2. * 凭证匹配器,用于做认证流程的凭证校验使用的类型
    3. * 其中有2个核心方法
    4. * 1. encode - 把明文密码,加密成密文密码
    5. * 2. matches - 校验明文和密文是否匹配
    6. * */
    7. public class MyMD5PasswordEncoder implements PasswordEncoder {
    8. /**
    9. * 加密
    10. * @param charSequence 明文字符串
    11. * @return
    12. */
    13. @Override
    14. public String encode(CharSequence charSequence) {
    15. try {
    16. MessageDigest digest = MessageDigest.getInstance("MD5");
    17. return toHexString(digest.digest(charSequence.toString().getBytes()));
    18. } catch (NoSuchAlgorithmException e) {
    19. e.printStackTrace();
    20. return "";
    21. }
    22. }
    23. /**
    24. * 密码校验
    25. * @param charSequence 明文,页面收集密码
    26. * @param s 密文 ,数据库中存放密码
    27. * @return
    28. */
    29. @Override
    30. public boolean matches(CharSequence charSequence, String s) {
    31. return s.equals(encode(charSequence));
    32. }
    33. /**
    34. * @param tmp 转16进制字节数组
    35. * @return 饭回16进制字符串
    36. */
    37. private String toHexString(byte [] tmp){
    38. StringBuilder builder = new StringBuilder();
    39. for (byte b :tmp){
    40. String s = Integer.toHexString(b & 0xFF);
    41. if (s.length()==1){
    42. builder.append("0");
    43. }
    44. builder.append(s);
    45. }
    46. return builder.toString();
    47. }
    48. }
    49. 复制代码

    2.在配置类中指定自定义密码凭证匹配器

    1. /**
    2. * 加密
    3. * @return 加密对象
    4. * 如需使用自定义密码凭证匹配器 返回自定义加密对象
    5. * 例如: return new MD5PasswordEncoder();
    6. */
    7. @Bean
    8. public PasswordEncoder passwordEncoder() {
    9. return new BCryptPasswordEncoder(); //Spring Security 自带
    10. }
    11. 复制代码

    登录配置

    方式一 转发

    1. http.formLogin()
    2. .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username
    3. .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password
    4. .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login
    5. .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login
    6. .failureForwardUrl("/failure"); // 登录失败后,请求转发的位置。Security请求转发使用Post请求。默认转发到: loginPage?error
    7. .successForwardUrl("/toMain"); // 用户登录成功后,请求转发到的位置。Security请求转发使用POST请求。
    8. 复制代码

    方式二 :重定向

    1. http.formLogin()
    2. .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username
    3. .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password
    4. .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login
    5. .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login
    6. .defaultSuccessUrl("/toMain",true); //用户登录成功后,响应重定向到的位置。 GET请求。必须配置绝对地址。
    7. .failureUrl("/failure"); // 登录失败后,重定向的位置。
    8. 复制代码

    方式三:自定义登录处理器

    自定义登录失败逻辑处理器

    1. /*自定义登录失败处理器*/
    2. public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    3. private String url;
    4. private boolean isRedirect;
    5. public MyAuthenticationFailureHandler(String url, boolean isRedirect) {
    6. this.url = url;
    7. this.isRedirect = isRedirect;
    8. }
    9. @Override
    10. public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    11. if (isRedirect){
    12. httpServletResponse.sendRedirect(url);
    13. }else {
    14. httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse);
    15. }
    16. }
    17. //get set 方法 省略
    18. 复制代码

    自定义登录成功逻辑处理器

    1. /**
    2. * 自定义登录成功后处理器
    3. * 转发重定向,有代码逻辑实现
    4. * */
    5. public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    6. private String url;
    7. private boolean isRedirect;
    8. public MyAuthenticationSuccessHandler(String url, boolean isRedirect) {
    9. this.url = url;
    10. this.isRedirect = isRedirect;
    11. }
    12. /**
    13. * @param request 请求对象 request.getRequestDispatcher.forward()
    14. * @param response 响应对象 response.sendRedirect()
    15. * @param authentication 用户认证成功后的对象。其中报换用户名权限结合,内容是
    16. * 自定义UserDetailsService
    17. * */
    18. @Override
    19. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    20. if (isRedirect){
    21. response.sendRedirect(url);
    22. }else {
    23. request.getRequestDispatcher(url).forward(request,response);
    24. }
    25. }
    26. //get set 方法 省略
    27. 复制代码
    1. http.formLogin()
    2. .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username
    3. .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password
    4. .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login
    5. .loginProcessingUrl("/login") // 用户登录逻辑请求地址是什么。 默认是 /login
    6. 复制代码

    登录相关配置类

    1. @Configuration
    2. @EnableWebSecurity
    3. public class SecurityConfig extends WebSecurityConfigurerAdapter {
    4. @Autowired
    5. private UserSecurity userSecurity;
    6. @Autowired
    7. private PersistentTokenRepository persistentTokenRepository;
    8. /**
    9. * 加密
    10. * @return 加密对象
    11. * 如需使用自定义加密逻辑 返回自定义加密对象
    12. * return new MD5PasswordEncoder(); return new SimplePasswordEncoder();
    13. */
    14. @Bean
    15. public PasswordEncoder passwordEncoder() {
    16. return new BCryptPasswordEncoder(); //Spring Security 自带
    17. }
    18. @Override
    19. protected void configure(HttpSecurity http) throws Exception {
    20. // 配置登录请求相关内容。
    21. http.formLogin()
    22. .loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么? 默认 /login
    23. .usernameParameter("name") // 设置请求参数中,用户名参数名称。 默认username
    24. .passwordParameter("pswd") // 设置请求参数中,密码参数名称。 默认password
    25. .loginProcessingUrl("/login") //设置登录 提交表单数据访问请求地址
    26. .defaultSuccessUrl("/toMain")
    27. .failureUrl("/toLogin");
    28. //.successForwardUrl("/toMain")
    29. //.failureForwardUrl("/toLogin");
    30. //.successHandler(new LoginSuccessHandler("/toMain", true)) //自定义登录成功处理器
    31. //.failureHandler(new LoginErrorHandler("/toLogin", true));
    32. http.authorizeRequests()
    33. //.antMatchers("/toLogin").anonymous() //只能匿名用户访问
    34. .antMatchers("/toLogin", "/register", "/login", "/favicon.ico").permitAll() // /toLogin请求地址,可以随便访问。
    35. .antMatchers("/**/*.js").permitAll() // 授予所有目录下的所有.js文件可访问权限
    36. .regexMatchers(".*[.]css").permitAll() // 授予所有目录下的所有.css文件可访问权限
    37. .anyRequest().authenticated(); // 任意的请求,都必须认证后才能访问。
    38. // 配置退出登录
    39. http.logout()
    40. .invalidateHttpSession(true) // 回收HttpSession对象。退出之前调用HttpSession.invalidate() 默认 true
    41. .clearAuthentication(true) // 退出之前,清空Security记录的用户登录标记。 默认 true
    42. // .addLogoutHandler() // 增加退出处理器。
    43. .logoutSuccessUrl("/") // 配置退出后,进入的请求地址。 默认是loginPage?logout
    44. .logoutUrl("/logout"); // 配置退出登录的路径地址。和页面请求地址一致即可。
    45. // 关闭CSRF安全协议。
    46. // 关闭是为了保证完整流程的可用。
    47. http.csrf().disable();
    48. }
    49. @Bean
    50. public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
    51. JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    52. jdbcTokenRepository.setDataSource(dataSource);
    53. //jdbcTokenRepository.setCreateTableOnStartup(true);
    54. return jdbcTokenRepository;
    55. }
    56. }
    57. 复制代码

    角色权限

    hasAuthority(String) 判断角色是否具有特定权限

    1. http.authorizeRequests().antMatchers("/main1.html").hasAuthority("admin")
    2. 复制代码

    hasAnyAuthority(String ...) 如果用户具备给定权限中某一个,就允许访问

    1. http.authorizeRequests().antMatchers("/admin/read").hasAnyAuthority("xxx","xxx")
    2. 复制代码

    hasRole(String) 如果用户具备给定角色就允许访问。否则出现403

    1. //请求地址为/admin/read的请求,必须登录用户拥有'管理员'角色才可访问
    2. http.authorizeRequests().antMatchers("/admin/read").hasRole("管理员")
    3. 复制代码

    hasAnyRole(String ...) 如果用户具备给定角色的任意一个,就允许被访问

    1. //用户拥有角色是管理员 或 访客 可以访问 /guest/read
    2. http.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理员", "访客")
    3. 复制代码

    hasIpAddress(String) 请求是指定的IP就运行访问

    1. //ip 是127.0.0.1 的请求 可以访问/ip
    2. http.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")
    3. 复制代码

    403 权限不足页面处理

    1.编写类实现接口AccessDeniedHandler

    1. /**
    2. * @describe 403 权限不足
    3. * @author: AnyWhere
    4. * @date 2021/4/18 20:57
    5. */
    6. @Component
    7. public class MyAccessDeniedHandler implements AccessDeniedHandler {
    8. @Override
    9. public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
    10. throws IOException, ServletException {
    11. response.setStatus(HttpServletResponse.SC_OK);
    12. response.setContentType("text/html;charset=UTF-8");
    13. response.getWriter().write(
    14. "" +
    15. "" +
    16. "
      " +
    17. "权限不足,请联系管理员" +
    18. "
      " +
  • "" +
  • ""
  • );
  • response.getWriter().flush();//刷新缓冲区
  • }
  • }
  • 复制代码
  • 2.配置类中配置exceptionHandling

    1. // 配置403访问错误处理器。
    2. http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/
    3. 复制代码

    RememberMe(记住我)

    1. @Configuration
    2. @EnableWebSecurity
    3. public class SecurityConfig extends WebSecurityConfigurerAdapter {
    4. @Override
    5. protected void configure(HttpSecurity http) throws Exception {
    6. //配置记住密码
    7. http.rememberMe()
    8. .rememberMeParameter("remember-me") // 修改请求参数名。 默认是remember-me
    9. .tokenValiditySeconds(14*24*60*60) // 设置记住我有效时间。单位是秒。默认是14天
    10. .rememberMeCookieName("remember-me") // 修改remember me的cookie名称。默认是remember-me
    11. .tokenRepository(persistentTokenRepository) // 配置用户登录标记的持久化工具对象。
    12. .userDetailsService(userSecurity); // 配置自定义的UserDetailsService接口实现类对象
    13. }
    14. @Bean
    15. public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){
    16. JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    17. jdbcTokenRepository.setDataSource(dataSource);
    18. //jdbcTokenRepository.setCreateTableOnStartup(true);
    19. return jdbcTokenRepository;
    20. }
    21. }
    22. 复制代码

    Spring Security 注解

    @Secured

    角色校验 ,请求到来访问控制单元方法时必须包含XX角色才能访问

    角色必须添加ROLE_前缀

    1. @Secured({"ROLE_管理员","ROLE_访客"})
    2. @RequestMapping("/toMain")
    3. public String toMain(){
    4. return "main";
    5. }
    6. 复制代码

    使用注解@Secured需要在配置类中添加注解 使@Secured注解生效

    1. @EnableGlobalMethodSecurity(securedEnabled = true)
    2. 复制代码

    @PreAuthorize

    权限检验,请求到来访问控制单元之前必须包含xx权限才能访问,控制单元方法执行前进行角色校验

    1. /**
    2. * [ROLE_管理员, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
    3. * @PreAuthorize 角色 、权限 校验 方法执行前进行角色校验
    4. *
    5. * hasAnyAuthority()
    6. * hasAuthority()
    7. *
    8. * hasPermission()
    9. *
    10. *
    11. * hasRole()
    12. * hasAnyRole()
    13. * */
    14. @PreAuthorize("hasAnyRole('ROLE_管理员','ROLE_访客')")
    15. @RequestMapping("/toMain")
    16. @PreAuthorize("hasAuthority('admin:write')")
    17. public String toMain(){
    18. return "main";
    19. }
    20. 复制代码

    使用@PreAuthorize@PostAuthorize 需要在配置类中配置注解@EnableGlobalMethodSecurity 才能生效

    1. @EnableGlobalMethodSecurity(prePostEnabled = true)
    2. 复制代码

    @PostAuthorize

    权限检验,请求到来访问控制单元之后必须包含xx权限才能访问 ,控制单元方法执行完后进行角色校验

    1. /**
    2. * [ROLE_管理员, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
    3. * @PostAuthorize 角色 、权限 校验 方法执行后进行角色校验
    4. *
    5. * hasAnyAuthority()
    6. * hasAuthority()
    7. * hasPermission()
    8. * hasRole()
    9. * hasAnyRole()
    10. * */
    11. @PostAuthorize("hasRole('ROLE_管理员')")
    12. @RequestMapping("/toMain")
    13. @PreAuthorize("hasAuthority('admin:write')")
    14. public String toMain(){
    15. return "main";
    16. }
    17. 复制代码

    Spring Security 整合Thymeleaf 进行权限校验

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-thymeleafartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.thymeleaf.extrasgroupId>
    7. <artifactId>thymeleaf-extras-springsecurity5artifactId>
    8. dependency>
    9. 复制代码

    Spring Security中CSRF

    什么是CSRF?

    CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack” 或者Session Riding。通过伪造用户请求访问受信任站点的非法请求访问。

    跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。

    客户端与服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。

    通俗解释:

    CSRF就是别的网站非法获取我们网站Cookie值,我们项目服务器是无法区分到底是不是我们的客户端,只有请求中有Cookie,认为是自己的客户端,所以这个时候就出现了CSRF

  • 相关阅读:
    ​LeetCode解法汇总1726. 同积元组
    java内部类学习总结/lambda表达式总结
    Pyqt5入门教程(1)--环境安装
    Docker Harbor概述及构建
    细粒度图像分类论文研读-2014
    【JDBCUtils ---DbUtils工具】
    C++类型转换详细说明
    Webgis系统调试与发布
    法国博士后招聘|国家健康与医学研究院(INSERM)-计算化学
    net mvc中使用vue自定义组件遇到的坑
  • 原文地址:https://blog.csdn.net/china_coding/article/details/127992765