当前位置:   article > 正文

Spring Security 自定义记住我功能!_security记住我自定义

security记住我自定义

目录

一、介绍

二、源码分析           

          ​编辑

三、传统web应用

四、前后端分离应用

4.1 自定义认证类 LoginFilter

4.2 ⾃定义 RememberMeService

4.3 配置应用


一、介绍

 本篇文章从方面进行讲解 :

  •   传统web 应用场景
  •   前后端分离应用场景       

二、源码分析           

        AbstractUserDetailsAuthenticationProvider类中authenticate⽅法在最后认证成功之后实现了记住我功能,但是查看源码得知如果开启记住我,必须进⾏相关的设置。

          6d3e8bc5e2f33dde0e73c230b0b92bbd.png

通过对源码的分析得出自定义的实现方式:

  1. 传统web方式
    1. 需要在所添加页面内添加一个 remember-me 的参数 值 为 true on yes 1
    2. 在配置类开启rememberMe 功能
  2. 前后端分离方式
    1. 自定义LoginFilter 继承 UsernamePasswordAuthenticationFilter 重写 attemptAuthentication 修改通 JSON 的方式获取值
    2. 自定义 RememberMeServices 的实现类 ,去重写rememberMeRequestd 方法,来修改成通过其他方式获取remember-me的值。
    3. 将重写后的类配置到 WebSecurityConfigurerAdapter 的继承类中
    4. 注意 rememberService 需要被配置两次 ,一次是在认证成功后使用的 ,一次是自动登录使用的。

三、传统web应用

        通过源码分析得知必须在认证请求中加⼊参数remember-me值为"true,on,yes,1"其中任意⼀个才可以完成记住我功能,这个时候修改认证界⾯:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>登录页面</title>
  6. </head>
  7. <body>
  8. <form action="/doLogin" method="post">
  9. 用户名:<input type="text" name="uName" /></br>
  10. 密码: <input type="password" name="pwdsss">
  11. 是否记住我 : <input type="checkbox" name="remember-me" value="true"/>
  12. <input type="submit" value="提交">
  13. </form>
  14. </body>
  15. </html>

配置中开启记住我

  1. @Configuration(proxyBeanMethods = false)
  2. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  3. /**
  4. * 自定义数据源
  5. * @return
  6. */
  7. @Override
  8. @Bean
  9. public UserDetailsService userDetailsService(){
  10. UserDetails userDetails = User.withUsername("zs").password("{noop}123").roles("admin").build();
  11. InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
  12. inMemoryUserDetailsManager.createUser(userDetails);
  13. return inMemoryUserDetailsManager;
  14. }
  15. /**
  16. * 定义安全规则
  17. */
  18. @Override
  19. protected void configure(HttpSecurity http) throws Exception {
  20. http.authorizeRequests()
  21. .mvcMatchers("/login.html").permitAll()
  22. .anyRequest().authenticated()
  23. .and()
  24. .rememberMe()// 开启记住我
  25. .and()
  26. .formLogin()
  27. .loginPage("/login.html")
  28. .loginProcessingUrl("/doLogin")
  29. .failureForwardUrl("/login.html")
  30. .usernameParameter("uName")
  31. .passwordParameter("pwdsss")
  32. .and()
  33. .csrf()
  34. .disable();
  35. }
  36. }

四、前后端分离应用

4.1 自定义认证类 LoginFilter

  1. package com.bjpowenrode.filter;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import org.springframework.http.MediaType;
  4. import org.springframework.security.authentication.AuthenticationServiceException;
  5. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  6. import org.springframework.security.core.Authentication;
  7. import org.springframework.security.core.AuthenticationException;
  8. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  9. import org.springframework.stereotype.Component;
  10. import org.springframework.util.ObjectUtils;
  11. import org.springframework.util.StringUtils;
  12. import javax.servlet.ServletInputStream;
  13. import javax.servlet.http.HttpServletRequest;
  14. import javax.servlet.http.HttpServletResponse;
  15. import java.io.IOException;
  16. import java.util.Map;
  17. import static org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY;
  18. /**
  19. * @author 千城丶Y
  20. * @className : LoginFilter
  21. * @date : 2022/9/3 14:50
  22. * @Description TODO 自定义前后端登录验证的Filter
  23. */
  24. public class LoginFilter extends UsernamePasswordAuthenticationFilter {
  25. private String passwordParameter =SPRING_SECURITY_FORM_PASSWORD_KEY;
  26. private String usernameParameter =SPRING_SECURITY_FORM_USERNAME_KEY;
  27. private String rememberMeParameter = SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY ;
  28. /**
  29. * 重写获取用户信息的方法
  30. */
  31. @Override
  32. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
  33. if (!request.getMethod().equals("POST")) {
  34. throw new AuthenticationServiceException(
  35. "Authentication method not supported: " + request.getMethod());
  36. }
  37. try {
  38. //2.判断是否是 json 格式请求类型
  39. if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
  40. // 获取JSON 流数据
  41. ServletInputStream inputStream = request.getInputStream();
  42. Map<String,String> map = new ObjectMapper().readValue(inputStream, Map.class);
  43. // 获取用户名
  44. String userName = map.get( usernameParameter);
  45. String pwd = map.get(passwordParameter);
  46. String rememberMe = map.get(rememberMeParameter);
  47. if (StringUtils.isEmpty(userName)){
  48. userName = "";
  49. }
  50. if (StringUtils.isEmpty(pwd)){
  51. pwd = "";
  52. }
  53. if (!ObjectUtils.isEmpty(rememberMe)){
  54. request.setAttribute(rememberMeParameter,rememberMe);
  55. }
  56. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
  57. userName, pwd);
  58. // Allow subclasses to set the "details" property
  59. setDetails(request, authRequest);
  60. return this.getAuthenticationManager().authenticate(authRequest);
  61. }else {
  62. throw new AuthenticationServiceException("认证参数格式不正确!");
  63. }
  64. } catch (IOException e) {
  65. e.printStackTrace();
  66. return null;
  67. }
  68. }
  69. @Override
  70. public void setPasswordParameter(String passwordParameter) {
  71. this.passwordParameter = passwordParameter;
  72. }
  73. @Override
  74. public void setUsernameParameter(String usernameParameter) {
  75. this.usernameParameter = usernameParameter;
  76. }
  77. public void setRememberMeParameter(String rememberMeParameter) {
  78. this.rememberMeParameter = rememberMeParameter;
  79. }
  80. }

4.2 ⾃定义 RememberMeService

  1. package com.bjpowenrode.service;
  2. import org.springframework.security.core.userdetails.UserDetailsService;
  3. import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
  4. import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
  5. import javax.servlet.http.HttpServletRequest;
  6. /**
  7. * @author 千城丶Y
  8. * @className : My
  9. * @date : 2022/9/3 15:20
  10. * @Description TODO 自定义 RememberMeService
  11. */
  12. public class MyPersistentTokenBasedRememberMeServices extends PersistentTokenBasedRememberMeServices {
  13. private boolean alwaysRemember;
  14. public MyPersistentTokenBasedRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
  15. super(key, userDetailsService, tokenRepository);
  16. }
  17. @Override
  18. protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
  19. if (alwaysRemember) {
  20. return true;
  21. }
  22. // 这里修改为从 请求作用域中获取
  23. String paramValue = request.getAttribute(parameter).toString();
  24. if (paramValue != null) {
  25. if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
  26. || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
  27. return true;
  28. }
  29. }
  30. if (logger.isDebugEnabled()) {
  31. logger.debug("Did not send remember-me cookie (principal did not set parameter '"
  32. + parameter + "')");
  33. }
  34. return false;
  35. }
  36. public boolean isAlwaysRemember() {
  37. return alwaysRemember;
  38. }
  39. @Override
  40. public void setAlwaysRemember(boolean alwaysRemember) {
  41. this.alwaysRemember = alwaysRemember;
  42. }
  43. }

4.3 配置应用

  1. package com.bjpowenrode.config;
  2. import com.bjpowenrode.filter.LoginFilter;
  3. import com.bjpowenrode.service.MyPersistentTokenBasedRememberMeServices;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.http.HttpMethod;
  8. import org.springframework.http.HttpStatus;
  9. import org.springframework.http.MediaType;
  10. import org.springframework.security.authentication.AuthenticationManager;
  11. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  12. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  13. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  14. import org.springframework.security.core.userdetails.User;
  15. import org.springframework.security.core.userdetails.UserDetailsService;
  16. import org.springframework.security.provisioning.InMemoryUserDetailsManager;
  17. import org.springframework.security.web.authentication.RememberMeServices;
  18. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  19. import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
  20. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
  21. import org.springframework.security.web.util.matcher.OrRequestMatcher;
  22. import java.util.HashMap;
  23. import java.util.Map;
  24. import java.util.UUID;
  25. /**
  26. * @author 千城丶Y
  27. * @className : WebSecurityConfig
  28. * @date : 2022/9/3 14:47
  29. * @Description TODO 安全认证类
  30. */
  31. @Configuration(proxyBeanMethods = true) // 这里如果是 false 则每次使用bean 时都会新建
  32. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  33. /**
  34. * 自定义数据源
  35. */
  36. @Override
  37. @Bean
  38. public UserDetailsService userDetailsService(){
  39. InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
  40. inMemoryUserDetailsManager.createUser(User.withUsername("zs").password("{noop}123").roles("admin").build());
  41. return inMemoryUserDetailsManager;
  42. }
  43. @Override
  44. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  45. auth.userDetailsService(userDetailsService());
  46. }
  47. /**
  48. * 暴露本地AuthenticationManagerBuilder
  49. */
  50. @Bean
  51. @Override
  52. public AuthenticationManager authenticationManagerBean() throws Exception {
  53. return super.authenticationManagerBean();
  54. }
  55. /**
  56. * 导入自定义Filter
  57. */
  58. @Bean
  59. public LoginFilter loginFilter () throws Exception {
  60. LoginFilter loginFilter = new LoginFilter();
  61. loginFilter.setUsernameParameter("userName");
  62. loginFilter.setPasswordParameter("pwd");
  63. loginFilter.setFilterProcessesUrl("/doLogin");
  64. loginFilter.setAuthenticationManager(authenticationManagerBean());
  65. loginFilter.setRememberMeServices(rememberMeServices()); // 设置 认证成功时使用自定义rememberMeService 认证成功后 生成cookie 的
  66. loginFilter.setAuthenticationSuccessHandler(
  67. (request,response,authentication)->{
  68. Map<String,Object> result = new HashMap<>(3);
  69. result.put("msg","登录成功!");
  70. result.put("code",200);
  71. result.put("authen",authentication);
  72. ObjectMapper objectMapper = new ObjectMapper();
  73. String json = objectMapper.writeValueAsString(result);
  74. response.setStatus(HttpStatus.OK.value());
  75. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  76. response.getWriter().println(json);
  77. }
  78. );
  79. loginFilter.setAuthenticationFailureHandler((request,response,authentication)->{
  80. Map<String,Object> result = new HashMap<>(3);
  81. result.put("msg","登录失败!");
  82. result.put("code",500);
  83. result.put("authen",authentication);
  84. ObjectMapper objectMapper = new ObjectMapper();
  85. String json = objectMapper.writeValueAsString(result);
  86. response.setStatus(HttpStatus.NO_CONTENT.value());
  87. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  88. response.getWriter().println(json);
  89. });
  90. return loginFilter ;
  91. }
  92. /**
  93. * 自定义安全
  94. */
  95. @Override
  96. protected void configure(HttpSecurity http) throws Exception {
  97. http.authorizeRequests()
  98. .mvcMatchers("/doLogin").permitAll()
  99. .anyRequest().authenticated()
  100. .and()
  101. .exceptionHandling()
  102. .authenticationEntryPoint((request,response,exception)->{
  103. Map<String,Object> result = new HashMap<>(3);
  104. result.put("msg","未登录!");
  105. result.put("code",401);
  106. result.put("error",exception.getMessage());
  107. ObjectMapper objectMapper = new ObjectMapper();
  108. String json = objectMapper.writeValueAsString(result);
  109. response.setStatus(HttpStatus.EXPECTATION_FAILED.value());
  110. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  111. response.getWriter().println(json);
  112. })
  113. .and()
  114. .formLogin()
  115. .usernameParameter("userName")
  116. .passwordParameter("pwd")
  117. .loginProcessingUrl("/doLogin")
  118. .successHandler((request,response,authentication)->{
  119. Map<String,Object> result = new HashMap<>(3);
  120. result.put("msg","登录成功!");
  121. result.put("code",200);
  122. result.put("authen",authentication);
  123. ObjectMapper objectMapper = new ObjectMapper();
  124. String json = objectMapper.writeValueAsString(result);
  125. response.setStatus(HttpStatus.OK.value());
  126. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  127. response.getWriter().println(json);
  128. })
  129. .failureHandler((request,response,authentication)->{
  130. Map<String,Object> result = new HashMap<>(3);
  131. result.put("msg","登录失败!");
  132. result.put("code",500);
  133. result.put("authen",authentication);
  134. ObjectMapper objectMapper = new ObjectMapper();
  135. String json = objectMapper.writeValueAsString(result);
  136. response.setStatus(HttpStatus.NO_CONTENT.value());
  137. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  138. response.getWriter().println(json);
  139. })
  140. .and()
  141. .rememberMe()
  142. .rememberMeServices(rememberMeServices()) // 设置自动登录时使用rememberService
  143. .and()
  144. .logout()
  145. .logoutRequestMatcher(new OrRequestMatcher(
  146. new AntPathRequestMatcher("/logout", HttpMethod.GET.name()),
  147. new AntPathRequestMatcher("/logout",HttpMethod.POST.name())
  148. ))
  149. .logoutSuccessHandler((res,resp,authentication)->{
  150. Map<String,Object> rs = new HashMap<>();
  151. rs.put("msg","退出登录成功");
  152. rs.put("用户信息",authentication);
  153. resp.setStatus(HttpStatus.OK.value());
  154. String json = new ObjectMapper().writeValueAsString(rs);
  155. resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
  156. resp.getWriter().println(json);
  157. })
  158. .and()
  159. .csrf()
  160. .disable();
  161. // 替换掉这个Filter
  162. http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
  163. }
  164. /**
  165. * 引入自定义RememberMeService
  166. */
  167. @Bean
  168. public RememberMeServices rememberMeServices(){
  169. // 这个 如果是用内存的话不能new 需要让他是对象 才可以,不然数据会不一致
  170. MyPersistentTokenBasedRememberMeServices myPersistentTokenBasedRememberMeServices = new MyPersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),userDetailsService(),new InMemoryTokenRepositoryImpl());
  171. return myPersistentTokenBasedRememberMeServices;
  172. }
  173. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/67668
推荐阅读
相关标签
  

闽ICP备14008679号