设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间16 现在时间16 缓存数据 “我不是一个含着金汤匙生的小孩, 我的家人,一直用他们自己的方式, 又普通又隆重地爱我” —张艺兴 ​​

“我不是一个含着金汤匙生的小孩, 我的家人,一直用他们自己的方式, 又普通又隆重地爱我” —张艺兴 ​​ -- 外婆

查看: 592|回复: 2

SpringSecurity实现自定义登录接口的详细过程

[复制链接]

  离线 

TA的专栏

  • 打卡等级:偶尔看看
  • 打卡总天数:14
  • 打卡月天数:0
  • 打卡总奖励:197
  • 最近打卡:2023-08-27 05:13:30
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
32
主题
26
精华
0
金钱
296
积分
68
注册时间
2023-8-13
最后登录
2025-5-31

发表于 2024-10-10 02:42:31 | 显示全部楼层 |阅读模式
目录
  • SpringSecurity实现自定义登录接口
    • 1、配置类 ConfigClazz(SpringSecuriey的)
    • 2、DIYUsernamePasswordAuthenticationFilter
    • 3、DIYAuthenticationProvider
    • 4、DIYAuthenticationManager
    • 5、MySQLUserDetailsManager
    • 6、控制层
    • 7、增强用户的实体类
    • 8、依赖

SpringSecurity实现自定义登录接口

1、配置类 ConfigClazz(SpringSecuriey的)

  1. //首先就是要有一个配置类
  2. @Resource
  3. private DIYUsernamePasswordAuthenticationFilter diyUsernamePasswordAuthenticationFilter;
  4. /*SpringSecurity配置*/
  5. @Bean
  6. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  7. http
  8. .authorizeRequests(
  9. authorize -> authorize
  10. .requestMatchers("/user/**","/").hasRole("user") //拥有user的角色可访问的接口
  11. .requestMatchers("/manager/**").hasRole("manager")//拥有manager的角色可访问的接口
  12. .requestMatchers("/login/**").permitAll()
  13. .anyRequest()
  14. .authenticated() // 任何请求都需要授权,重定向到
  15. );
  16. /*登录页*/
  17. http.formLogin(AbstractHttpConfigurer::disable);//禁用默认的登录接口,使用自定义的登录接口
  18. /*登出*/
  19. http.logout(logout ->{
  20. logout
  21. .logoutUrl("/goOut").permitAll()
  22. //登录退出成功,向前端返回json格式的字符串
  23. .logoutSuccessHandler((HttpServletRequest request, HttpServletResponse response, Authentication authentication)->{
  24. Map<String, String[]> parameterMap = request.getParameterMap();
  25. //进入登录页时,判断是否已经登陆过 TowLogin 参数
  26. if(!parameterMap.isEmpty() && parameterMap.get("TowLogin")[0].equals("true")){
  27. String json = JSON.toJSONString(Code.NOTowLogin);
  28. response.setContentType("application/json;charset=UTF-8");
  29. response.getWriter().println(json);
  30. } else {
  31. String json = JSON.toJSONString(Code.SuccessLogout);
  32. response.setContentType("application/json;charset=UTF-8");
  33. response.getWriter().println(json);
  34. }
  35. });
  36. });
  37. /*向过滤器链中添加自定义的过滤器
  38. 用自定义的过滤器代替 UsernamePasswordAuthenticationFilter 过滤器
  39. */
  40. http.addFilterAfter(diyUsernamePasswordAuthenticationFilter, LogoutFilter.class);
  41. /*请求异常处理*/
  42. http.exceptionHandling(exception ->{
  43. /*用户未登录时,访问限权接口,返回 json 格式的字符串
  44. 这个配是。把页面跳转交给前端,即:用户未登录时,后端只返回 json 格式的字符串,不会跳转页面
  45. -- 未登录时,重定向的 url .loginPage("/login/getLoginHTML").permitAll(),就不起作用了 --
  46. */
  47. exception.authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)->{
  48. String json = JSON.toJSONString(Code.NoLogin);
  49. response.setContentType("application/json;charset=UTF-8");
  50. response.getWriter().println(json);
  51. });
  52. //响应登录用户访问未授权路径时(user角色访问manager角色的接口) 有 未授权 json 提示
  53. exception.accessDeniedHandler((HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)->{
  54. String json = JSON.toJSONString(Code.Forbidden);
  55. response.setContentType("application/json;charset=UTF-8");
  56. response.getWriter().println(json);
  57. });
  58. });
  59. /*会话管理*/
  60. http.sessionManagement(session -> {
  61. session
  62. //表示,最大连接数量为 1 ,同一个账号,最多只能在一台设备上登录,当第二个登陆时,会把第一个挤掉
  63. .maximumSessions(1)
  64. //挤掉后,对前端返回的json字符串
  65. .expiredSessionStrategy((SessionInformationExpiredEvent event)->{
  66. String json = JSON.toJSONString(Code.ForeignLogin);
  67. HttpServletResponse response = event.getResponse();
  68. response.setContentType("application/json;charset=UTF-8");
  69. response.getWriter().println(json);
  70. });
  71. });
  72. /*开启跨域访问*/
  73. http.cors(withDefaults());
  74. /* 禁用csrf的防御手段。
  75. * 开启后,相当于每次前端访问接口的时候
  76. * 都需要携带_crsf为参数名的参数,功能类似于 token,
  77. * 因此建议禁用
  78. * */
  79. http.csrf(AbstractHttpConfigurer::disable);
  80. return http.build();
  81. }
  82. //设置密码的编码方式(必须有)
  83. @Bean
  84. public BCryptPasswordEncoder bCryptPasswordEncoder(){
  85. return new BCryptPasswordEncoder(10);
  86. }
复制代码

解释 _scrf 在哪看,只有最初有,后面就没有,但是如果不携带,就不让你访问接口,因此建议禁用

2、DIYUsernamePasswordAuthenticationFilter

该类用于替换 UsernamePasswordAuthenticationFilter 过滤器,应用自己自定义的过滤器

  1. @Component //相当于 UsernamePasswordAuthenticationFilter
  2. public class DIYUsernamePasswordAuthenticationFilter extends OncePerRequestFilter {
  3. @Override
  4. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  5. /*问题:不能读取请求体中的信息,因为是一次性的,读完,后面就不能用了
  6. * 因此,这里避免用json格式传输 账号 和 密码
  7. * */
  8. //获取非 json 格式传输的,OK了,只要前端给 json 格式 的token就能获取了
  9. Map<String, String[]> parameterMap = request.getParameterMap(); //有前端打开
  10. SUser user = null;
  11. HttpSession session = request.getSession();
  12. //有前端打开
  13. //检查token,通过token解析出用户的账号,根据账号,从 session 中查询
  14. if(parameterMap.get("token") != null)
  15. user = (SUser)session.getAttribute(parameterMap.get("token")[0]);
  16. if (user == null) {
  17. //放行,表示已经退出,需要重新验证,区别就是有没有 存入SecurityContextHolder 一步骤
  18. filterChain.doFilter(request, response);
  19. return;
  20. }
  21. //存入SecurityContextHolder,获取权限信息封装到Authentication中
  22. UsernamePasswordAuthenticationToken authenticationToken = // 没有前端获取用户数据目前先这样写
  23. new UsernamePasswordAuthenticationToken(user,user.getPassword(),user.getAuthorities());
  24. SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  25. //验证成功,放行
  26. filterChain.doFilter(request, response);
  27. }
  28. }
复制代码

3、DIYAuthenticationProvider

该类是发放授权的接口

  1. @Component
  2. public class DIYAuthenticationProvider implements AuthenticationProvider {
  3. @Resource
  4. private UserDetailsService userDetailsService;
  5. @Resource
  6. private PasswordEncoder passwordEncoder;
  7. @Override
  8. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  9. String username = authentication.getName();
  10. String password = (String) authentication.getCredentials();
  11. // 从数据库中加载用户信息
  12. UserDetails userDetails = userDetailsService.loadUserByUsername(username);
  13. // 检查密码是否正确
  14. if (!passwordEncoder.matches(password, userDetails.getPassword())) {
  15. throw new BadCredentialsException("用户名或密码错误");
  16. }
  17. // 创建一个已认证的 Authentication 对象
  18. UsernamePasswordAuthenticationToken authenticatedToken =
  19. new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
  20. authenticatedToken.setDetails(authentication.getDetails());
  21. return authenticatedToken;
  22. }
  23. @Override
  24. public boolean supports(Class<?> authentication) {
  25. return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
  26. }
  27. }
复制代码

4、DIYAuthenticationManager

该类是用来调用发放授权接口

  1. @Component
  2. public class DIYAuthenticationManager implements AuthenticationManager {
  3. @Resource //这里虽然是注入的接口,但是由于自定义的类 DIYAuthenticationProvider 实现了该接口,因此优先使用
  4. AuthenticationProvider authenticationProvider;
  5. //这里其实可以调用默认的 授权提供者,有匹配的就会授权,但是,没必要,因为肯定匹配不了,最后还是用自己的,
  6. @Override //那不如 直接就用自己的就好了
  7. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  8. return authenticationProvider.authenticate(authentication);
  9. }
  10. }
复制代码

5、MySQLUserDetailsManager

该类用于获取用户的信息

  1. @Component //将这个类交给Spring容器管理,即:创建该类的 bean 对象,进而取代(重写)原来的方法
  2. public class MySQLUserDetailsManager implements UserDetailsService{
  3. //由于是基于数据库的,因此,只需要实现一个 UserDetailsService 接口就好,不需要实现其他的接口
  4. @Resource //这个是Mapper接口,用于从数据库中调用查询信息
  5. SUserMapper sUserMapper;
  6. @Resource //这个是必要的
  7. HttpServletRequest request;
  8. @Override //String username
  9. public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
  10. //获取数据信息要在这里开始,由于只暴露用户输入account,因此数据库中的数据只能,所有的 account都不一样,才能唯一匹配 account,这里 Email 一定不一样
  11. //这里的 username 就是用户输入的账号,为了方便,就换一个变量名 account
  12. List<SUser> sUsers = sUserMapper.selectAllByEmail(account); //这里 Email 一定不一样
  13. if(sUsers != null && !sUsers.isEmpty()) {
  14. SUser sUser = sUsers.get(0);
  15. //这里把 authenticate 这个用户的信息存到session中,如果调用退出登录接口,就会删除session里面的内容
  16. HttpSession session = request.getSession();
  17. session.setAttribute(String.valueOf(sUser.getEmail()),sUser);
  18. return sUser;
  19. } else {
  20. throw new UsernameNotFoundException(account);
  21. }
  22. }
  23. }
复制代码

6、控制层

  1. @Controller
  2. @Tag(name = "登录注册")
  3. @RequestMapping("/login")
  4. public class LoginController {
  5. @Resource
  6. private SUserService sUserService;
  7. @Resource
  8. private AuthenticationManager authenticationManager;
  9. @GetMapping("/getLoginHTML") //进入登录页的接口
  10. public String getLoginHtml(HttpSession session){
  11. boolean aNew = session.isNew();
  12. if(aNew)
  13. return "login";
  14. //如果一个浏览器试图登录两次,那么就会直接调用退出接口
  15. return "redirect:/goOut?TowLogin=true";
  16. }
  17. @PostMapping("/ooo") //由于是自定义登录接口,因此什么请求都可以,建议用Post
  18. @ResponseBody //将返回值写入响应体中
  19. public Code login(String account,String password){
  20. SUser sUser = new SUser();
  21. sUser.setEmail(account);
  22. sUser.setPassword(password);
  23. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sUser,password);
  24. Authentication authenticate = authenticationManager.authenticate(authenticationToken);
  25. if(Objects.isNull(authenticate))
  26. throw new AuthenticationCredentialsNotFoundException("用户账号或密码错误");
  27. else{
  28. //这里响应回去一个 token,根据账号加密后,生成的 token
  29. Map<String, String> map = new HashMap<>();
  30. map.put("token",authenticate.getName());
  31. return new Code<>(Code.OK, map);
  32. }
  33. }
复制代码

7、增强用户的实体类

这里由于要封装用户的详细信息,而用 MybatisX 生成的 User 实体类不能满足需求,因此要实现一个接口

  1. @TableName(value ="s_user")
  2. @Data
  3. @Repository //将这个类交给IOC容器(Spring)管理
  4. public class SUser implements Serializable , UserDetails{ //实现这个接口
  5. /**
  6. * 主键id,自动递增
  7. */
  8. @TableId(type = IdType.AUTO)
  9. private Integer id;
  10. /**
  11. * 用户名:<=10
  12. */
  13. private String name;
  14. /**
  15. * 年龄
  16. */
  17. private Integer age;
  18. /**
  19. * 性别:女 , 男
  20. */
  21. private String sex;
  22. /**
  23. * 邮箱账号:<=30
  24. */
  25. private String email;
  26. /**
  27. * 密码:<=15
  28. */
  29. private String password;
  30. /**
  31. * 是否被禁用:0-未禁用,1-已禁用
  32. */
  33. private Integer isForbidden;
  34. /**
  35. * 该账号的角色:0-普通用户,1-管理员
  36. */
  37. private String role;
  38. /**
  39. * 是否被删除(或用户注销):0-未删除,1-删除
  40. */
  41. @TableLogic
  42. private Integer isDelete;
  43. @Serial
  44. @TableField(exist = false)
  45. private static final long serialVersionUID = 1L;
  46. @Override
  47. public Collection<? extends GrantedAuthority> getAuthorities() {
  48. /*这里要自己拼接 ROLE_ + role
  49. * ROLE_ : 是固定的
  50. * 由于我这里的实体类设计的是:String role; 不是数组形式,因此不用循环
  51. * 如果是数组形式的限权,循环遍历,并创建 SimpleGrantedAuthority 就好了
  52. * */
  53. List<SimpleGrantedAuthority> list = new ArrayList<>();
  54. list.add(new SimpleGrantedAuthority("ROLE_" + role));
  55. return list;
  56. }
  57. @Override //注意:这里的用户名是 账号
  58. public String getUsername() {
  59. return this.email;
  60. }
  61. @Override//没有这个设定就返回通过的结果,可以用翻译 isAccountNonExpired ? 在每个方法名后加一个? 问自己是true/false
  62. public boolean isAccountNonExpired() {
  63. return true;
  64. }
  65. @Override//自己的实体类中有这个设定,就返回判断的结果
  66. public boolean isAccountNonLocked() {
  67. return isForbidden == 1;
  68. }
  69. @Override
  70. public boolean isCredentialsNonExpired() {
  71. return true;
  72. }
  73. @Override
  74. public boolean isEnabled() {
  75. return true;
  76. }
  77. }
复制代码

8、依赖

java版本 17

springBoot版本 3.2.0

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-test</artifactId>
  9. <scope>test</scope>
  10. </dependency>
  11. <!--SpringSecurity依赖-->
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-security</artifactId>
  15. </dependency>
  16. <!--thymeleaf作为视图模板-->
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  20. </dependency>
  21. <!--mybatis-Puls的依赖-->
  22. <dependency>
  23. <groupId>com.baomidou</groupId>
  24. <artifactId>mybatis-plus-boot-starter</artifactId>
  25. <version>3.5.4.1</version>
  26. <!--由于SpringBoot的版本太高,需要这样1-->
  27. <exclusions>
  28. <exclusion>
  29. <groupId>org.mybatis</groupId>
  30. <artifactId>mybatis-spring</artifactId>
  31. </exclusion>
  32. </exclusions>
  33. </dependency>
  34. <!--由于SpringBoot的版本太高,需要这样2-->
  35. <dependency>
  36. <groupId>org.mybatis</groupId>
  37. <artifactId>mybatis-spring</artifactId>
  38. <version>3.0.3</version>
  39. </dependency>
  40. <!--mysql的驱动包-->
  41. <dependency>
  42. <groupId>mysql</groupId>
  43. <artifactId>mysql-connector-java</artifactId>
  44. <version>8.0.30</version>
  45. </dependency>
  46. <!--简化实体类开发-->
  47. <dependency>
  48. <groupId>org.projectlombok</groupId>
  49. <artifactId>lombok</artifactId>
  50. </dependency>
  51. <!--JavaWeb组件-->
  52. <dependency>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-web</artifactId>
  55. </dependency>
  56. <!--引入json数据依赖,用于给前端返回json类型的数据-->
  57. <dependency>
  58. <groupId>com.alibaba.fastjson2</groupId>
  59. <artifactId>fastjson2</artifactId>
  60. <version>2.0.37</version>
  61. </dependency>
  62. <!--knife4j测试,对请求的测试,有两种,swagger-ui.html / doc.html 都可以-->
  63. <dependency>
  64. <groupId>com.github.xiaoymin</groupId>
  65. <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
  66. <version>4.4.0</version>
  67. </dependency>
  68. </dependencies>
复制代码

到此这篇关于SpringSecurity实现自定义登录接口的文章就介绍到这了,更多相关SpringSecurity自定义登录接口内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:无名新人
  • 打卡总天数:1
  • 打卡月天数:0
  • 打卡总奖励:15
  • 最近打卡:2024-06-18 09:36:48
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
27
积分
4
注册时间
2023-12-3
最后登录
2024-6-18

发表于 2025-2-28 12:59:20 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
11
积分
2
注册时间
2023-9-12
最后登录
2023-9-12

发表于 2025-4-7 04:34:56 | 显示全部楼层
顶顶更健康!!!
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表