Spring Boot开发Spring Security

发布时间:2024年01月23日

????????这里我对springboot不做过多描述,因为我觉得学这个的肯定掌握了springboot这些基础

导入核心依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐security</artifactId>
</dependency>

Servlet Context配置

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //默认Url根路径跳转到/login,此url为spring security提供
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("redirect:/login");
    }
}

Spring Security为我们提供了登录页面,这里我是将 "/",路径设置为登陆页面的路径,方便测试,

也可以自定义登录页面,我会在后面说明

application.properties配置文件

server.port=8080
server.servlet.context-path=/security-springboot
spring.application.name = security-springboot

spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp

spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=XXXX
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

? ? ? ? 至于为什么不是yml,这完全不是这里的重点,关于前端用的jsp,各位看官姥爷们也凑合看吧,理解这个框架就好,最下面的数据库配置这里也可以先不做,后面也会详细说明

核心配置来喽,WebSecurityConfig

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //定义用户信息服务(查询用户信息)
/*
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }
*/

    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
//                .antMatchers("/r/r1").hasAuthority("p2")
//                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .loginPage("/login-view")//登录页面
                .loginProcessingUrl("/login")
                .successForwardUrl("/login-success")//自定义登录成功的页面地址
        .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login-view?logout");


    }
}

????????如果不想连接数据库测试,这里可以先把这些注释解除掉去掉,用模拟数据

controller代码

@RestController
public class LoginController {

    @RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
    public String loginSuccess(){
        //提示具体用户名称登录成功
        return getUsername()+" 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
    public String r1(){
        return "访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
    public String r2(){
        return " 访问资源2";
    }

}

工作原理

Spring Security 所解决的问题就是 安全访问控制 ,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,
校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过 Filter AOP 等技术来实现, Spring
Security Web 资源的保护是靠 Filter 实现的,所以从这个 Filter 来入手,逐步深入 Spring Security 原理。
当初始化 Spring Security 时,会创建一个名为 SpringSecurityFilterChain Servlet 过滤器,类型为
org.springframework.security.web.FilterChainProxy ,它实现了 javax.servlet.Filter ,因此外部的请求会经过此
类,下图是 Spring Security 过虑器链结构图:
FilterChainProxy 是一个代理,真正起作用的是 FilterChainProxy SecurityFilterChain 所包含的各个 Filter ,同时
这些 Filter 作为 Bean Spring 管理,它们是 Spring Security 核心,各有各的职责,但他们并不直接处理用户的
,也不直接处理用户的 授权 ,而是把它们交给了认证管理器( AuthenticationManager )和决策管理器
AccessDecisionManager )进行处理,下图是 FilterChainProxy 相关类的 UML 图示。
spring Security 功能的实现主要是由一系列过滤器链相互配合完成。
下面介绍过滤器链中主要的几个过滤器及其作用:
SecurityContextPersistenceFilter 这个 Filter 是整个拦截过程的入口和出口(也就是第一个和最后一个拦截
器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext ,然后把它设置给
SecurityContextHolder 。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好
SecurityContextRepository ,同时清除 securityContextHolder 所持有的 SecurityContext
UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密
码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler
AuthenticationFailureHandler ,这些都可以根据需求做相关改变;
FilterSecurityInterceptor 是用于保护 web 资源的,使用 AccessDecisionManager 对当前用户进行授权访问,前
面已经详细介绍过了;
ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:
AuthenticationException AccessDeniedException ,其它的异常它会继续抛出。

认证流程

1. 用户提交用户名、密码被 SecurityFilterChain 中的 UsernamePasswordAuthenticationFilter 过滤器获取到,
封装为请求 Authentication ,通常情况下是 UsernamePasswordAuthenticationToken 这个实现类。
2. 然后过滤器将 Authentication 提交至认证管理器( AuthenticationManager )进行认证
3. 认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,
身份信息,细节信息,但密码通常会被移除) Authentication 实例。
4. SecurityContextHolder 安全上下文容器将第 3 步填充了信息的 Authentication ,通过
SecurityContextHolder.getContext().setAuthentication(…) 方法,设置到其中。
可以看出 AuthenticationManager 接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它
的实现类为 ProviderManager 。而 Spring Security 支持多种认证方式,因此 ProviderManager 维护着一个
List<AuthenticationProvider> 列表,存放多种认证方式,最终实际的认证工作是由
AuthenticationProvider 完成的。咱们知道 web 表单的对应的 AuthenticationProvider 实现类为
DaoAuthenticationProvider ,它的内部又维护着一个 UserDetailsService 负责 UserDetails 的获取。最终
AuthenticationProvider UserDetails 填充至 Authentication

AuthenticationProvider

通过前面的 Spring Security 认证流程 我们得知,认证管理器( AuthenticationManager )委托
AuthenticationProvider 完成认证工作。
AuthenticationProvider 是一个接口,定义如下:
public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    boolean supports(Class<?> var1);
}
authenticate () 方法定义了 认证的实现过程 ,它的参数是一个 Authentication ,里面包含了登录用户所提交的用
户、密码等。而返回值也是一个 Authentication ,这个 Authentication 则是在认证成功后,将用户的权限及其他信
息重新组装后生成。
Spring Security 中维护着一个 List<AuthenticationProvider> 列表,存放多种认证方式,不同的认证方式使用不
同的 AuthenticationProvider 。如使用用户名密码登录时,使用 AuthenticationProvider1 ,短信登录时使用
AuthenticationProvider2 等等这样的例子很多。
每个 AuthenticationProvider 需要实现 supports () 方法来表明自己支持的认证方式,如我们使用表单方式认证,
在提交请求时 Spring Security 会生成 UsernamePasswordAuthenticationToken ,它是一个 Authentication ,里面
封装着用户提交的用户名、密码信息。而对应的,哪个 AuthenticationProvider 来处理它?
我们在 DaoAuthenticationProvider 的基类 AbstractUserDetailsAuthenticationProvider 发现以下代码:
public boolean supports(Class<?> authentication) {
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
也就是说当 web 表单提交用户名密码时, Spring Security DaoAuthenticationProvider 处理。
最后,我们来看一下 Authentication ( 认证信息 ) 的结构,它是一个接口,我们之前提到的
UsernamePasswordAuthenticationToken 就是它的实现之一:
public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
1 Authentication spring security 包中的接口,直接继承自 Principal 类,而 Principal 是位于 java.security
包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个 getName() 方法。
2 getAuthorities() ,权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系
列字符串。
3 getCredentials() ,凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
4 getDetails() ,细节信息, web 应用中的实现接口通常为 WebAuthenticationDetails ,它记录了访问者的 ip 地 址和sessionId 的值。
5 getPrincipal() ,身份信息,大部分情况下返回的是 UserDetails 接口的实现类, UserDetails 代表用户的详细
信息,那从 Authentication 中取出来的 UserDetails 就是当前登录用户信息,它也是框架中的常用接口之一。

UserDetailsService

现在咱们现在知道 DaoAuthenticationProvider 处理了 web 表单的认证逻辑,认证成功后既得到一个
Authentication(UsernamePasswordAuthenticationToken 实现 ) ,里面包含了身份信息( Principal )。这个身份 信息就是一个 Object ,大多数情况下它可以被强转为 UserDetails 对象。
DaoAuthenticationProvider 中包含了一个 UserDetailsService 实例,它负责根据用户名提取用户信息
UserDetails( 包含密码 ) ,而后 DaoAuthenticationProvider 会去对比 UserDetailsService 提取的用户密码与用户提交
的密码是否匹配作为认证成功的关键依据,因此可以通过将自定义的 UserDetailsService 公开为 spring bean 来定 义自定义身份验证。
很多人把 DaoAuthenticationProvider UserDetailsService 的职责搞混淆,其实 UserDetailsService 只负责从特定 的地方(通常是数据库)加载用户信息,仅此而已。而DaoAuthenticationProvider 的职责更大,它完成完整的认 证流程,同时会把UserDetails 填充至 Authentication
上面一直提到 UserDetails 是用户信息,咱们看一下它的真面目:
public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}
它和 Authentication 接口很类似,比如它们都拥有 username authorities Authentication getCredentials() 与 UserDetails中的 getPassword() 需要被区分对待,前者是用户提交的密码凭证,后者是用户实际存储的密码,认证 其实就是对这两者的比对。Authentication 中的 getAuthorities() 实际是由 UserDetails getAuthorities() 传递而形 成的。还记得Authentication 接口中的 getDetails() 方法吗?其中的 UserDetails 用户详细信息便是经过了 AuthenticationProvider认证之后被填充的。
通过实现 UserDetailsService UserDetails ,我们可以完成对用户信息获取方式以及用户信息字段的扩展。
Spring Security 提供的 InMemoryUserDetailsManager( 内存认证 ) JdbcUserDetailsManager(jdbc 认证 ) 就是 UserDetailsService的实现类,主要区别无非就是从内存还是从数据库加载用户

PasswordEncoder

DaoAuthenticationProvider 认证处理器通过 UserDetailsService 获取到 UserDetails 后,它是如何与请求 Authentication中的密码做对比呢?
在这里 Spring Security 为了适应多种多样的加密类型,又做了抽象, DaoAuthenticationProvider 通过 PasswordEncoder接口的 matches 方法进行密码的对比,而具体的密码对比细节取决于实现

public interface PasswordEncoder {
    String encode(CharSequence var1);
    boolean matches(CharSequence var1, String var2);
    default boolean upgradeEncoding(String encodedPassword) {
    return false;
    }
}
Spring Security 提供很多内置的 PasswordEncoder ,能够开箱即用,使用某种 PasswordEncoder 只需要进行如
下声明即可,如下:
@Bean
public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}
NoOpPasswordEncoder 采用字符串匹配方法,不对密码进行加密比较处理,密码比较流程如下:
1 、用户输入密码(明文 )
2 DaoAuthenticationProvider 获取 UserDetails (其中存储了用户的正确密码)
3 DaoAuthenticationProvider 使用 PasswordEncoder 对输入的密码和正确的密码进行校验,密码一致则校验通
过,否则校验失败。
NoOpPasswordEncoder 的校验规则拿 输入的密码和 UserDetails 中的正确密码进行字符串比较,字符串内容一致
则校验通过,否则 校验失败。

实际项目中推荐使用 BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder 等,感兴趣
的大家可以看看这些 PasswordEncoder 的具体实现。

授权流程

通过 快速上手 我们知道, Spring Security 可以通过 http.authorizeRequests() web 请求进行授权保护。 Spring
Security 使用标准 Filter 建立了对 web 请求的拦截,最终实现对资源的授权访问。

1. 拦截请求 ,已认证用户访问受保护的 web 资源将被 SecurityFilterChain 中的 FilterSecurityInterceptor 的子
类拦截。
2. 获取资源访问策略 FilterSecurityInterceptor 会从 SecurityMetadataSource 的子类
DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限
Collection<ConfigAttribute>
SecurityMetadataSource 其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则, 读
取访问策略如:

(不过后面我们都会从数据库中拿)

3. 最后, FilterSecurityInterceptor 会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资
源,否则将禁止访问

授权决策

AccessDecisionManager 采用 投票 的方式来确定是否能够访问受保护资源。

AffirmativeBased 的逻辑是:
1 )只要有 AccessDecisionVoter 的投票为 ACCESS_GRANTED 则同意用户进行访问;
2 )如果全部弃权也表示通过;
3 )如果没有一个人投赞成票,但是有人投反对票,则将抛出 AccessDeniedException
Spring security 默认使用的是 AffirmativeBased
ConsensusBased 的逻辑是:
1 )如果赞成票多于反对票则表示通过。
2 )反过来,如果反对票多于赞成票则将抛出 AccessDeniedException
3 )如果赞成票与反对票相同且不等于 0 ,并且属性 allowIfEqualGrantedDeniedDecisions 的值为 true ,则表 示通过,否则将抛出异常AccessDeniedException 。参数 allowIfEqualGrantedDeniedDecisions 的值默认为 true
4 )如果所有的 AccessDecisionVoter 都弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定,如果该值
true 则表示通过,否则将抛出异常 AccessDeniedException 。参数 allowIfAllAbstainDecisions 的值默认为 false
UnanimousBased 的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递
AccessDecisionVoter 进行投票,而 UnanimousBased 会一次只传递一个 ConfigAttribute
AccessDecisionVoter 进行投票。这也就意味着如果我们的 AccessDecisionVoter 的逻辑是只要传递进来的
ConfigAttribute 中有一个能够匹配则投赞成票,但是放到 UnanimousBased 中其投票结果就不一定是赞成了。
UnanimousBased 的逻辑具体来说是这样的:
1 )如果受保护对象配置的某一个 ConfigAttribute 被任意的 AccessDecisionVoter 反对了,则将抛出
AccessDeniedException
2 )如果没有反对票,但是有赞成票,则表示通过。
3 )如果全部弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定, true 则通过, false 则抛出
AccessDeniedException

自定义认证

自定义登录页面

快速上手 中,你可能会想知道登录页面从哪里来的?因为我们并没有提供任何的 HTML JSP 文件。 Spring
Security 的默认配置没有明确设定一个登录页面的 URL ,因此 Spring Security 会根据启用的功能自动生成一个登录
页面 URL ,并使用默认 URL 处理登录的提交内容,登录后跳转的到默认 URL 等等。尽管自动生成的登录页面很方便
快速启动和运行,但大多数应用程序都希望定义自己的登录页面。

@Configuration//就相当于springmvc.xml文件
public class WebConfig implements WebMvcConfigurer {


    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("redirect:/login-view");
        registry.addViewController("/login-view").setViewName("login");

    }

}
//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/r/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin() (1)
.loginPage("/login‐view") (2)
.loginProcessingUrl("/login") (3)
.successForwardUrl("/login‐success") (4)
.permitAll();
}
1 )允许表单登录
2 )指定我们自己的登录页 ,spring security 以重定向方式跳转到 /login-view
3 )指定登录处理的 URL ,也就是用户名、密码表单提交的目的路径
4 )指定登录成功后的跳转 URL
5 )我们必须允许所有用户访问我们的登录页(例如为验证的用户),这个 formLogin().permitAll() 方法允许
任意用户访问基于表单登录的所有的 URL

问题解决

spring security 为防止 CSRF Cross-site request forgery 跨站请求伪造)的发生,限制了除了 get 以外的大多数方法。
解决方法 1
屏蔽 CSRF 控制,即 spring security 不再限制 CSRF
配置 WebSecurityConfig
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable() //屏蔽CSRF控制,即spring security不再限制CSRF
    ...
}

连接数据库认证

????????创建数据库

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
CREATE TABLE `t_user` (
    `id` bigint(20) NOT NULL COMMENT '用户id',
    `username` varchar(64) NOT NULL,
    `password` varchar(64) NOT NULL,
    `fullname` varchar(255) NOT NULL COMMENT '用户姓名',
    `mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC

application.properties配置

spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=mysql
spring.datasource.driver‐class‐name=com.mysql.jdbc.Driver
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql‐connector‐java</artifactId>
    <version>5.1.47</version>
</dependency>

pom.xml添加依赖,mysql版本根据自己情况

定义模型类型,在model包定义UserDto

@Data
public class UserDto {
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}

Dao包定义UserDao

@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public UserDto getUserByUsername(String username){
String sql ="select id,username,password,fullname from t_user where username = ?";
List<UserDto> list = jdbcTemplate.query(sql, new Object[]{username}, new
BeanPropertyRowMapper<>(UserDto.class));
if(list == null && list.size() <= 0){
return null;
}
return list.get(0);
}
}

定义UserDetailService

@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登录账号
System.out.println("username="+username);
//根据账号去数据库查询...
UserDto user = userDao.getUserByUsername(username);
if(user == null){
return null;
}
//这里暂时使用静态数据
UserDetails userDetails =
User.withUsername(user.getFullname()).password(user.getPassword()).authorities("p1").build();
return userDetails;
}
}

使用BCryptPasswordEncoder

WebSecurityConfig中
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
UserDetails 中的密码存储 BCrypt 格式
前边实现了从数据库查询用户信息,所以数据库中的密码应该存储 BCrypt 格式

会话

获取用户身份

编写 LoginController ,实现 /r/r1 /r/r2 的测试资源,并修改 loginSuccess 方法,注意 getUsername 方法, Spring
Security 获取当前登录用户信息的方法为 SecurityContextHolder.getContext().getAuthentication()
@RestController
public class LoginController {

    @RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
    public String loginSuccess(){
        //提示具体用户名称登录成功
        return getUsername()+" 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
    public String r1(){
        return getUsername()+" 访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
    public String r2(){
        return getUsername()+" 访问资源2";
    }

    //获取当前用户信息
    private String getUsername(){
        String username = null;
        //当前认证通过的用户身份
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //用户身份
        Object principal = authentication.getPrincipal();
        if(principal == null){
            username = "匿名";
        }
        if(principal instanceof org.springframework.security.core.userdetails.UserDetails){
            UserDetails userDetails = (UserDetails) principal;
            username = userDetails.getUsername();
        }else{
            username = principal.toString();
        }
        return username;
    }
}

会话控制

通过以下配置方式对该选项进行配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}
默认情况下, Spring Security 会为每个登录成功的用户会新建一个 Session ,就是 ifRequired
若选用 never ,则指示 Spring Security 对登录成功的用户不创建 Session 了,但若你的应用程序在某地方新建了
session ,那么 Spring Security 会用它的。
若使用 stateless ,则说明 Spring Security 对登录成功的用户不会创建 Session 了,你的应用程序也不会允许新建
session 。并且它会暗示不使用 cookie ,所以每个请求都需要重新进行身份验证。这种无状态架构适用于 REST API
及其无状态认证机制。

会话超时

spring boot 配置文件
server.servlet.session.timeout = 3600s
session 超时之后,可以通过 Spring Security 设置跳转的路径
http.sessionManagement()
    .expiredUrl("/login‐view?error=EXPIRED_SESSION")
    .invalidSessionUrl("/login‐view?error=INVALID_SESSION");
expired session 过期, invalidSession 指传入的 sessionid 无效

安全会话cookie

我们可以使用 httpOnly secure 标签来保护我们的会话 cookie
httpOnly :如果为 true ,那么浏览器脚本将无法访问 cookie
secure :如果为 true ,则 cookie 将仅通过 HTTPS 连接发送
spring boot 配置文件:
server.servlet.session.cookie.http‐only = true
server.servlet.session.cookie.secure = true

退出

Spring security 默认实现了 logout 退出,访问 /logout ,果然不出所料,退出功能 Spring 也替我们做好了。
点击 “Log Out” 退出 成功。
退出 后访问其它 url 判断是否成功退出。
这里也可以自定义退出成功的页面
.and()
    .logout()
    .logoutUrl("/logout")
    .logoutSuccessUrl("/login‐view?logout");
当退出操作出发时,将发生:
????????使HTTP Session 无效
????????清除 SecurityContextHolder
????????跳转到 /login - view?logout
但是,类似于配置登录功能,咱们可以进一步自定义退出功能:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
//...
.and()
.logout() (1)
.logoutUrl("/logout") (2)
.logoutSuccessUrl("/login‐view?logout") (3)
.logoutSuccessHandler(logoutSuccessHandler) (4)
.addLogoutHandler(logoutHandler) (5)
.invalidateHttpSession(true); (6)
}
1 )提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用
2 )设置触发退出操作的 URL ( 默认是 /logout ).
3 )退出之后跳转的 URL 。默认是 /login?logout
4 )定制的 LogoutSuccessHandler ,用于实现用户退出成功时的处理。如果指定了这个选项那么
logoutSuccessUrl() 的设置会被忽略。
5 )添加一个 LogoutHandler ,用于实现用户退出时的清理工作 . 默认 SecurityContextLogoutHandler 会被添加
为最后一个 LogoutHandler
6 )指定是否在退出时让 HttpSession 无效。 默认设置为 true
注意:如果让 logout GET 请求下生效,必须关闭防止 CSRF 攻击 csrf().disable() 。如果开启了 CSRF ,必须使用
post 方式请求 /logout
logoutHandler
一般来说, LogoutHandler 的实现类被用来执行必要的清理,因而他们不应该抛出异常。
下面是 Spring Security 提供的一些实现:
PersistentTokenBasedRememberMeServices 基于持久化 token RememberMe 功能的相关清理
TokenBasedRememberMeService 基于 token RememberMe 功能的相关清理
CookieClearingLogoutHandler 退出时 Cookie 的相关清理
CsrfLogoutHandler 负责在退出时移除 csrfToken
SecurityContextLogoutHandler 退出时 SecurityContext 的相关清理
链式 API 提供了调用相应的 LogoutHandler 实现的快捷方式,比如 deleteCookies()
文章来源:https://blog.csdn.net/weixin_53535434/article/details/135755375
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。