前端带你学后端系列 ⑤【安全框架Spring Security】
2023-12-16 16:41:35
前端带你学后端系列 ⑤【安全框架Spring Security】
Ⅰ 什么是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
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private AuthenticationMapper authenticationMapper;
/*
UserDetailsService 只有 loadUserByUsername 一个接口方法。
用于查询数据库中的数据,将来做比对用。
*/
@Override
public 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);
}
@Autowired
public void setAuthenticationMapper(AuthenticationMapper authenticationMapper) {
this.authenticationMapper = authenticationMapper;
}
}
AuthenticationProvider
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
private UserDetailsServiceImpl userDetailsServiceImpl;
private BCryptPasswordEncoder bCryptPasswordEncoder;
/*
Authentication authenticate():定义了用于处理认证逻辑的接口标准.
*/
@Override
public 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. 将权限信息也封装进去,返回给AuthenticationManager
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}
throw new AuthenticationException("用户密码错误") {
};
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
@Autowired
public void setBCryptPasswordEncoder(BCryptPasswordEncoder bCryptPasswordEncoder) {
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Autowired
public void setUserDetailsServiceImpl(UserDetailsServiceImpl userDetailsServiceImpl) {
this.userDetailsServiceImpl = userDetailsServiceImpl;
}
}
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private MyAuthenticationProvider myAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) {
// 配置自定义的校验器
auth.authenticationProvider(myAuthenticationProvider);
}
// ~ Bean
// -----------------------------------------------------------------------------------------------------------------
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// ~ Autowired
// -----------------------------------------------------------------------------------------------------------------
@Autowired
public void setMyAuthenticationProvider(MyAuthenticationProvider myAuthenticationProvider) {
this.myAuthenticationProvider = myAuthenticationProvider;
}
}
④ 认证流程
Ⅱ Spring Security应用
① HTTP基本认证
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//1.配置基本认证方式
http.authorizeRequests()
//对任意请求都进行认证
.anyRequest()
.authenticated()
.and()
//开启basic认证。默认就是这个
.httpBasic();
}
}
② Form认证
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 用来定义哪些请求需要忽略安全控制,哪些请求必须接受安全控制;还可以在合适的时候清除SecurityContext以避免内存泄漏,
* 同时也可以用来定义请求防火墙和请求拒绝处理器,另外我们开启Spring Security Debug模式也是这里配置的
*/
@Override
public void configure(WebSecurity web) throws Exception {
//super.configure(web);
web.ignoring()
.antMatchers("/js/**", "/css/**", "/images/**");
}
@Override
protected 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:携带登录的用户名及角色等信息
*/
@Override
public 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();
}
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected 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:异常信息
*/
@Override
public 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();
}
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected 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>
② 验证码配置类
/**
* 验证码配置,用于配置验证码的边框,图片,字体等大小。
*/
@Configuration
public class KaptchaConfig {
//验证码的配置类
@Bean
DefaultKaptcha 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;
}
}
③ 创建控制层,给前端返回验证码的图片
/**
* 创建验证码图片,返回前端。
*/
@Controller
public class CaptchaController {
@Autowired
private Producer captchaProducer;
@GetMapping("/captcha")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 设置内容类型
response.setContentType("image/jpeg");
// 创建验证码文本
String capText = captchaProducer.createText();
// 将验证码文本设置到session
request.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();
@Override
protected 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 {
@Override
protected 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);
}
@Bean
public 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?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
username: root
password: syc
security:
remember-me:
key: mySecret
② 配置类中配置
@Override
protected 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 {
private String passwordParameter =SPRING_SECURITY_FORM_PASSWORD_KEY;
private String usernameParameter =SPRING_SECURITY_FORM_USERNAME_KEY;
private String rememberMeParameter = SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY ;
/**
* 重写获取用户信息的方法
*/
@Override
public 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" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}else {
throw new AuthenticationServiceException("认证参数格式不正确!");
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public void setPasswordParameter(String passwordParameter) {
this.passwordParameter = passwordParameter;
}
@Override
public 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);
}
@Override
protected 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;
}
@Override
public void setAlwaysRemember(boolean alwaysRemember) {
this.alwaysRemember = alwaysRemember;
}
}
③ 配置
@Configuration(proxyBeanMethods = true) // 这里如果是 false 则每次使用bean 时都会新建
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 自定义数据源
*/
@Override
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("zs").password("{noop}123").roles("admin").build());
return inMemoryUserDetailsManager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
/**
* 暴露本地AuthenticationManagerBuilder
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 导入自定义Filter
*/
@Bean
public 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 ;
}
/**
* 自定义安全
*/
@Override
protected 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();
// 替换掉这个Filter
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
}
/**
* 引入自定义RememberMeService
*/
@Bean
public RememberMeServices rememberMeServices(){
// 这个 如果是用内存的话不能new 需要让他是对象 才可以,不然数据会不一致
MyPersistentTokenBasedRememberMeServices myPersistentTokenBasedRememberMeServices = new MyPersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),userDetailsService(),new InMemoryTokenRepositoryImpl());
return myPersistentTokenBasedRememberMeServices;
}
}
// 将自定义的Filter放在指定Filter后
http.addFilterAfter();
// 将自定义的Filter放在指定Filter后
http.addFilterBefore();
// 将自定义的Filter替换指定的原生Filter
http.addFilterAt();
// 在这里我们使用的addFilterAt(),将原生用户认证Filter替换为自定义的Filter:
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
文章来源:https://blog.csdn.net/sugerfle/article/details/135024239
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!