前端带你学后端系列 ⑤【安全框架Spring Security】
- Ⅰ 什么是Spring Security?
- ① Spring Security概念
- ② Spring Security 本质
- ③ Spring Security核心组件
- ① 用图片详细展示
- ② 用代码详细展示 AuthenticationManager 与 AuthenticationProvider 与 UserDetailsService的关系
- ④ 认证流程
- Ⅱ Spring Security应用
- ① HTTP基本认证
- ② Form认证
- ③ 前后端分离时的安全处理方案(最常用)
- ④ 基于过滤器实现图形验证码
- ① 添加依赖
- ② 验证码配置类
- ③ 创建控制层,给前端返回验证码的图片
- ④ 自定义过滤器链,且放在【UsernamePasswordAuthenticationFilter】过滤器的前面
- ⑤ 配置Security配置类
- ⑤ Remember-Me 记住我功能(传统web)
- ① yml文件配置加密令牌的key
- ② 配置类中配置
- ⑤ Remember-Me 记住我功能(前后端分离)
- ① 自定义认证类 LoginFilter
- ② 自定义 RememberMeService
- ③ 配置
Ⅰ 什么是Spring Security?
① Spring Security概念
Spring Security 是基于 Spring 的【身份认证】和【用户授权】,其中核心技术使用了 【Servlet 过滤器】、【IOC】 和【AOP】等。
【身份认证】:用户去访问系统资源时,系统要求验证用户的身份信息。拿着登录网站的用户名密码和数据库的比对
。
【用户授权】:当身份认证通过后,去访问系统的资源,给用户分配角色进而权限
。
② Spring Security 本质
Spring Security的本质是
过滤器链
,Security会依次按照顺序执行过滤器链
接下来,我们认识一下这些过滤器链
各个过滤器链的作用
- SecurityContextPersistenceFilter:是【过滤链】的开始,它会确认当前【用户的认证信息】,放入
【 SecurityContextHolder 】
当中。 - HeaderWriterFilter:往请求的 Header 中添加一些信息。
- CorsFilter:用于处理
【跨域请求】
。 - LogoutFilter:
匹配【退出请求】
,默认为【 /logout】
,清理认证信息。 - UsernamePasswordAuthenticationFilter:
匹配【登录请求】
,默认为 【/login 的 POST 请求】
,用来封装表单提交的【用户名密码】,封装成【UsernamePasswordAuthenticationToken】
,更新数据到【 SecurityContextHolder 】
中。 ExceptionTranslationFilter
:异常过滤,处于整个链【后部】,用来转化访问异常
AccessDeniedException 和认证异常
AuthenticationExceptionFilterSecurityInterceptor
:用于判断和处理请求访问需要的权限,角色等信息。
③ Spring Security核心组件
- SecurityContextHolder:
容器
。用于【存储】已经登陆用户的【信息】。它通常包含了当前登录用户的信息,例如用户名、密码和角色等。 - SecurityContext:
持有Authentication
,被SecurityContextHolder 所持有
。 - Authentication:在
用户登录认证之前
,用户名密码等信息
会被封装为一个Authentication
的具体实现类对象。在登录认证成功之后
又会生成
一个信息更全面的Authentication对象
。放到SecurityContextHolder中,供全局使用
。 - AuthenticationManager:它【处理】来自用户的身份验证请求,并基于这些请求
【返回认证对象】
。 - AuthenticationProvider:
【具体】的身份验证机制
,例如表单身份验证。生成一个【 Authentication 对象】
【返回】给【 AuthenticationManager】
- UserDetailsService:定义了从
【内存、关系数据库】
中【获取】用户详细信息的方法。UserDetailsService 只有loadUserByUsername 一个接口
方法, 用于通过用户名获取用户数据.返回 UserDetails 对象
, 表示用户的核心信息 (用户名, 用户密码, 权限等信息)。
① 用图片详细展示
- SecurityContextHolder与SecurityContext与Authentication的关系。
- AuthenticationManager 与 AuthenticationProvider 与 UserDetailsService的关系
② 用代码详细展示 AuthenticationManager 与 AuthenticationProvider 与 UserDetailsService的关系
UserDetailsService
@Servicepublic class UserDetailsServiceImpl implements UserDetailsService {private AuthenticationMapper authenticationMapper;/* UserDetailsService 只有 loadUserByUsername 一个接口方法。 用于查询数据库中的数据,将来做比对用。*/@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {//1. 根据用户名查询数据库,查到对应的用户MyUser myUser = authenticationMapper.loadUserByUsername(name);// ... 做一些异常处理,没有找到用户之类的if (myUser == null) {throw new UsernameNotFoundException("用户不存在");}//2. 根据用户ID,查询用户的角色List<Role> roles = authenticationMapper.findRoleByUserId(myUser.getId());// 添加角色List<GrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getName()));}//3. 构建 Security 的 User 对象,返回给AuthenticationProvider return new User(myUser.getName(), myUser.getPassword(), authorities);}@Autowiredpublic void setAuthenticationMapper(AuthenticationMapper authenticationMapper) {this.authenticationMapper = authenticationMapper;}}
AuthenticationProvider
@Componentpublic class MyAuthenticationProvider implements AuthenticationProvider {private UserDetailsServiceImpl userDetailsServiceImpl;private BCryptPasswordEncoder bCryptPasswordEncoder;/* Authentication authenticate():定义了用于处理认证逻辑的接口标准. */@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {//1. 获取用户输入的用户名和密码final String username = authentication.getName();final String password = authentication.getCredentials().toString();//2. 获取userDetailsServiceImpl返回的对象UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username);//3. 进行密码的比对boolean flag = bCryptPasswordEncoder.matches(password, userDetails.getPassword());// 校验通过if (flag) {//4. 将权限信息也封装进去,返回给AuthenticationManagerreturn new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());}throw new AuthenticationException("用户密码错误") {};}@Overridepublic boolean supports(Class<" />> aClass) {return true;}@Autowiredpublic void setBCryptPasswordEncoder(BCryptPasswordEncoder bCryptPasswordEncoder) {this.bCryptPasswordEncoder = bCryptPasswordEncoder;}@Autowiredpublic void setUserDetailsServiceImpl(UserDetailsServiceImpl userDetailsServiceImpl) {this.userDetailsServiceImpl = userDetailsServiceImpl;}}
SecurityConfig
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {private MyAuthenticationProvider myAuthenticationProvider;@Overrideprotected void configure(AuthenticationManagerBuilder auth) {// 配置自定义的校验器auth.authenticationProvider(myAuthenticationProvider);}// ~ Bean// -----------------------------------------------------------------------------------------------------------------@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}// ~ Autowired// -----------------------------------------------------------------------------------------------------------------@Autowiredpublic void setMyAuthenticationProvider(MyAuthenticationProvider myAuthenticationProvider) {this.myAuthenticationProvider = myAuthenticationProvider;}}
④ 认证流程
Ⅱ Spring Security应用
① HTTP基本认证
@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {//1.配置基本认证方式http.authorizeRequests()//对任意请求都进行认证.anyRequest().authenticated().and()//开启basic认证。默认就是这个.httpBasic();}}
② Form认证
@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {/** * 用来定义哪些请求需要忽略安全控制,哪些请求必须接受安全控制;还可以在合适的时候清除SecurityContext以避免内存泄漏, * 同时也可以用来定义请求防火墙和请求拒绝处理器,另外我们开启Spring Security Debug模式也是这里配置的 */@Overridepublic void configure(WebSecurity web) throws Exception {//super.configure(web);web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");}@Overrideprotected void configure(HttpSecurity http) throws Exception {//super.configure(http);//3.进一步配置自定义的登录页面 //拦截请求,创建FilterSecurityInterceptor http.authorizeRequests() .anyRequest().authenticated()//用and来表示配置过滤器结束,以便进行下一个过滤器的创建和配置.and() //设置表单登录,创建UsernamePasswordAuthenticationFilter.formLogin().loginPage("/myLogin.html").permitAll()//指登录成功后,是否始终跳转到登录成功url。它默认为false.defaultSuccessUrl("/index.html",true)//post登录接口,登录验证由系统实现.loginProcessingUrl("/login")//用户密码错误跳转接口.failureUrl("/error.html")//要认证的用户参数名,默认username.usernameParameter("username")//要认证的密码参数名,默认password.passwordParameter("password").and()//配置注销.logout()//注销接口.logoutUrl("/logout")//注销成功后跳转到的接口.logoutSuccessUrl("/myLogin.html").permitAll()//删除自定义的cookie.deleteCookies("myCookie").and()//注意:需禁用crsf防护功能,否则登录不成功.csrf().disable();}}
指定自定义登陆页面与error页面
<body><div class="login"><h2>Access Form</h2><div class="login-top"><h1>登录验证</h1><form action="/login" method="post"><input type="text" name="username" placeholder="username" /><input type="password" name="password" placeholder="password" /><div class="forgot"><a href="#">忘记密码</a><input type="submit" value="登录" ></div></form></div><div class="login-bottom"><h3>新用户 <a href="#">注 册</a></h3></div></div></body>
③ 前后端分离时的安全处理方案(最常用)
简单版的代码实现
/** * 处理登录成功时的业务逻辑 */public class SecurityAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {/** * Authentication:携带登录的用户名及角色等信息 */@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {//直接输出json格式的响应信息Object principal = authentication.getPrincipal();response.setContentType("application/json;charset=utf-8");PrintWriter out = response.getWriter();//以json格式对外输出身份信息out.write(new ObjectMapper().writeValueAsString(principal));out.flush();out.close();}}
@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().permitAll()//认证成功时的处理器.successHandler(new SecurityAuthenticationSuccessHandler()).and().csrf().disable();}}
同样,配置异常的也是如此
/** * 处理登录失败时的业务逻辑 */public class SecurityAuthenticationFailureHandler extends ExceptionMappingAuthenticationFailureHandler {/** * AuthenticationException:异常信息 */@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {//直接输出json格式的响应信息response.setContentType("application/json;charset=utf-8");PrintWriter out = response.getWriter();out.write(e.getMessage());out.flush();out.close();}}
@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().permitAll()//认证成功时的处理器.successHandler(new SecurityAuthenticationSuccessHandler())//认证失败时的处理器.failureHandler(new SecurityAuthenticationFailureHandler()).and().csrf().disable();}}
④ 基于过滤器实现图形验证码
我们推荐使用github上的开源验证码解决方案kaptcha
具体的代码步骤
① 添加依赖
<!--验证码--><dependency><groupId>com.github.axet</groupId><artifactId>kaptcha</artifactId><version>0.0.9</version></dependency>
② 验证码配置类
/** * 验证码配置,用于配置验证码的边框,图片,字体等大小。 */ @Configurationpublic class KaptchaConfig { //验证码的配置类@BeanDefaultKaptcha producer() {Properties properties = new Properties(); properties.put("kaptcha.border", "no"); //边框properties.put("kaptcha.textproducer.font.color", "black"); //字体颜色properties.put("kaptcha.textproducer.char.space", "5"); //字体间隔properties.put("kaptcha.image.height", "40"); //图片高度properties.put("kaptcha.image.width", "100"); //图片宽度properties.put("kaptcha.textproducer.font.size", "30"); //字体大小Config config = new Config(properties); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config);return defaultKaptcha;}}
③ 创建控制层,给前端返回验证码的图片
/** *创建验证码图片,返回前端。 */ @Controllerpublic class CaptchaController {@Autowiredprivate Producer captchaProducer;@GetMapping("/captcha")public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {// 设置内容类型response.setContentType("image/jpeg");// 创建验证码文本String capText = captchaProducer.createText();// 将验证码文本设置到sessionrequest.getSession().setAttribute("captcha", capText);// 创建验证码图片BufferedImage bi = captchaProducer.createImage(capText);// 获取响应输出流ServletOutputStream out = response.getOutputStream();// 将图片验证码数据写到响应输出流ImageIO.write(bi, "jpg", out);// 推送并关闭响应输出流try {out.flush();} finally {out.close();}}}
④ 自定义过滤器链,且放在【UsernamePasswordAuthenticationFilter】过滤器的前面
/** * 基于过滤器实现图形验证码的验证功能,这属于Servlet层面,简单易理解. * 主要的作用: *1. 【重点】放在【UsernamePasswordAuthenticationFilter】前面。 *2. 如果走的不是“/login”接口,那么不走验证码的验证。 *3. 如果走的是“/login”,则走验证码的功能。验证成功以后,放行。 */public class VerificationCodeFilter extends OncePerRequestFilter {private AuthenticationFailureHandler authenticationFailureHandler = new SecurityAuthenticationFailureHandler();@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 非登录请求不校验验证码,直接放行if (!"/login".equals(httpServletRequest.getRequestURI())) {filterChain.doFilter(httpServletRequest, httpServletResponse);} else {try {//校验验证码verificationCode(httpServletRequest);//验证码校验通过后,对请求进行放行filterChain.doFilter(httpServletRequest, httpServletResponse);} catch (VerificationCodeException e) {authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);}}}public void verificationCode (HttpServletRequest httpServletRequest) throws VerificationCodeException {HttpSession session = httpServletRequest.getSession();String savedCode = (String) session.getAttribute("captcha");if (!StringUtils.isEmpty(savedCode)) {// 随手清除验证码,不管是失败还是成功,所以客户端应在登录失败时刷新验证码session.removeAttribute("captcha");}String requestCode = httpServletRequest.getParameter("captcha");// 校验不通过抛出异常if (StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(savedCode) || !requestCode.equals(savedCode)) {throw new VerificationCodeException();}}}
⑤ 配置Security配置类
@EnableWebSecurity(debug = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/app/api/**", "/captcha")// antMatchers 放行验证码.permitAll().anyRequest().authenticated().and().csrf().disable();//将过滤器添加在UsernamePasswordAuthenticationFilter之前http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);}@Beanpublic PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}}
总结一下步骤
- 放行验证码的api请求,
前端
登陆页面展示验证码图片
。- 自定义验证码Filter,
且放在UsernamePasswordAuthenticationFilter之前
2.1.1 如果走的是login
方法,则走验证码验证的方法
2.1.2 如果不走login
方法,则放行。
⑤ Remember-Me 记住我功能(传统web)
具体的步骤
① yml文件配置加密令牌的key
spring:datasource:url: jdbc:mysql://localhost:3306/db-security" />=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMTusername: rootpassword: sycsecurity:remember-me:key: mySecret
② 配置类中配置
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().loginPage("/login/page").loginProcessingUrl("/login").and().authorizeRequests().antMatchers("/login/page").permitAll().anyRequest().authenticated().and().rememberMe() //记住我功能.tokenRepository(jdbcTokenRepository())//保存登录信息.tokenValiditySeconds(60 * 60 * 24 * 7); //记住我有效时长;http.csrf().disable();}
⑤ Remember-Me 记住我功能(前后端分离)
具体的步骤
① 自定义认证类 LoginFilter
public class LoginFilter extends UsernamePasswordAuthenticationFilter { privateString passwordParameter=SPRING_SECURITY_FORM_PASSWORD_KEY;privateString usernameParameter=SPRING_SECURITY_FORM_USERNAME_KEY;private String rememberMeParameter = SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY ;/** * 重写获取用户信息的方法 */@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (!request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} try {//2.判断是否是 json 格式请求类型if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {// 获取JSON 流数据ServletInputStream inputStream = request.getInputStream();Map<String,String> map = new ObjectMapper().readValue(inputStream, Map.class); // 获取用户名 String userName = map.get( usernameParameter);String pwd = map.get(passwordParameter);String rememberMe = map.get(rememberMeParameter); if (StringUtils.isEmpty(userName)){userName = "";}if (StringUtils.isEmpty(pwd)){pwd = "";} if (!ObjectUtils.isEmpty(rememberMe)){request.setAttribute(rememberMeParameter,rememberMe);} UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, pwd); // Allow subclasses to set the "details" propertysetDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest);}else {throw new AuthenticationServiceException("认证参数格式不正确!");} } catch (IOException e) {e.printStackTrace();return null;}} @Overridepublic void setPasswordParameter(String passwordParameter) {this.passwordParameter = passwordParameter;} @Overridepublic void setUsernameParameter(String usernameParameter) {this.usernameParameter = usernameParameter;} public void setRememberMeParameter(String rememberMeParameter) {this.rememberMeParameter = rememberMeParameter;}}
② 自定义 RememberMeService
public class MyPersistentTokenBasedRememberMeServices extends PersistentTokenBasedRememberMeServices { private boolean alwaysRemember;public MyPersistentTokenBasedRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {super(key, userDetailsService, tokenRepository);} @Overrideprotected boolean rememberMeRequested(HttpServletRequest request, String parameter) {if (alwaysRemember) {return true;}// 这里修改为从 请求作用域中获取String paramValue = request.getAttribute(parameter).toString();if (paramValue != null) {if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")|| paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {return true;}}if (logger.isDebugEnabled()) {logger.debug("Did not send remember-me cookie (principal did not set parameter '"+ parameter + "')");} return false;} public boolean isAlwaysRemember() {return alwaysRemember;} @Overridepublic void setAlwaysRemember(boolean alwaysRemember) {this.alwaysRemember = alwaysRemember;}}
③ 配置
@Configuration(proxyBeanMethods = true) // 这里如果是 false 则每次使用bean 时都会新建public class WebSecurityConfigextends WebSecurityConfigurerAdapter { /** * 自定义数据源 */@Override@Beanpublic UserDetailsService userDetailsService(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("zs").password("{noop}123").roles("admin").build());return inMemoryUserDetailsManager;} @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService());} /** * 暴露本地AuthenticationManagerBuilder */@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();} /** * 导入自定义Filter */@Beanpublic LoginFilter loginFilter () throws Exception {LoginFilter loginFilter = new LoginFilter();loginFilter.setUsernameParameter("userName");loginFilter.setPasswordParameter("pwd");loginFilter.setFilterProcessesUrl("/doLogin"); loginFilter.setAuthenticationManager(authenticationManagerBean());loginFilter.setRememberMeServices(rememberMeServices()); // 设置 认证成功时使用自定义rememberMeService 认证成功后 生成cookie 的loginFilter.setAuthenticationSuccessHandler((request,response,authentication)->{Map<String,Object> result = new HashMap<>(3);result.put("msg","登录成功!");result.put("code",200);result.put("authen",authentication); ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(result);response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.getWriter().println(json); });loginFilter.setAuthenticationFailureHandler((request,response,authentication)->{Map<String,Object> result = new HashMap<>(3);result.put("msg","登录失败!");result.put("code",500);result.put("authen",authentication); ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(result);response.setStatus(HttpStatus.NO_CONTENT.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.getWriter().println(json); });return loginFilter ;} /** * 自定义安全 */@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().mvcMatchers("/doLogin").permitAll().anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint((request,response,exception)->{Map<String,Object> result = new HashMap<>(3);result.put("msg","未登录!");result.put("code",401);result.put("error",exception.getMessage()); ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(result);response.setStatus(HttpStatus.EXPECTATION_FAILED.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.getWriter().println(json);}).and().formLogin().usernameParameter("userName").passwordParameter("pwd").loginProcessingUrl("/doLogin").successHandler((request,response,authentication)->{Map<String,Object> result = new HashMap<>(3);result.put("msg","登录成功!");result.put("code",200);result.put("authen",authentication); ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(result);response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.getWriter().println(json); }).failureHandler((request,response,authentication)->{Map<String,Object> result = new HashMap<>(3);result.put("msg","登录失败!");result.put("code",500);result.put("authen",authentication); ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(result);response.setStatus(HttpStatus.NO_CONTENT.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.getWriter().println(json); }).and().rememberMe().rememberMeServices(rememberMeServices()) // 设置自动登录时使用rememberService.and().logout().logoutRequestMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/logout", HttpMethod.GET.name()),new AntPathRequestMatcher("/logout",HttpMethod.POST.name()))).logoutSuccessHandler((res,resp,authentication)->{Map<String,Object> rs = new HashMap<>();rs.put("msg","退出登录成功");rs.put("用户信息",authentication);resp.setStatus(HttpStatus.OK.value());String json = new ObjectMapper().writeValueAsString(rs); resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); resp.getWriter().println(json);}).and().csrf().disable(); // 替换掉这个Filterhttp.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);} /** * 引入自定义RememberMeService */@Beanpublic RememberMeServices rememberMeServices(){// 这个 如果是用内存的话不能new 需要让他是对象 才可以,不然数据会不一致MyPersistentTokenBasedRememberMeServices myPersistentTokenBasedRememberMeServices = new MyPersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),userDetailsService(),new InMemoryTokenRepositoryImpl());return myPersistentTokenBasedRememberMeServices;}}
//将自定义的Filter放在指定Filter后http.addFilterAfter();//将自定义的Filter放在指定Filter后http.addFilterBefore();//将自定义的Filter替换指定的原生Filterhttp.addFilterAt();
// 在这里我们使用的addFilterAt(),将原生用户认证Filter替换为自定义的Filter:http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END