Spring Boot开发Spring Security

news2024/12/23 22:28:36

        这里我对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()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1408261.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何优雅的实现前端国际化?

JavaScript 中每个常见问题都有许多成熟的解决方案。当然&#xff0c;国际化 (i18n) 也不例外&#xff0c;有很多成熟的 JavaScript i18n 库可供选择&#xff0c;下面就来分享一些热门的前端国际化库&#xff01; i18next i18next 是一个用 JavaScript 编写的全面的国际化框架…

Apipost自动化测试+Jenkins实现持续集成

Apipost 自动化测试支持「持续集成」功能&#xff0c;在安装了Apipost的服务器中输入命令&#xff0c;即可运行测试脚本。 创建自动化测试脚本 在创建好的测试用例中选择「持续集成」。 点击新建&#xff0c;配置运行环境、循环次数、间隔停顿后点击保存会生成命令。 安装 Ap…

C++ STL之stack的使用及模拟实现

文章目录 1. 介绍2. stack的使用3. 栈的模拟实现 1. 介绍 英文解释&#xff1a; 也就是说&#xff1a; stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 stack是作为容器适配器被实现…

Java JVM垃圾回收 JVM调优 老年代 新生代

如何判断对象可以回收 引用计数法 当一个对象被其他对象引用&#xff0c;该对象计数 1&#xff0c;当某个对象不再引用该对象&#xff0c;其计数 -1当一个对象没有被其他对象引用时&#xff0c;即计数为0&#xff0c;该对象就可以被回收 缺点&#xff1a;循环引用时&#xf…

全桥RLC模态图具体分析

T0时刻&#xff0c;Q6,Q7,Q1.Q4开通&#xff0c;驱动为高电平&#xff0c;励磁电流线性上升,但是lm电流在to是为负电流&#xff0c;这时刻有给副边提供能量&#xff0c;Ip电流开始上升&#xff0c;这个时候给副边的电流也是从0开始上升,这个能量由励磁电感提供&#xff0c;Co给…

HCIP-BGP实验4

搭建实验拓扑图 要求 1.全网可达 2.isp只能配置IP地址 实验开始 配置IP地址及环回 r1,r2,r9,r10配ipv4地址(以r1为例) [Huawei]sysname r1 [r1]interface g0/0/0 [r1-GigabitEthernet0/0/0]ip address 12.1.1.1 24 [r1-GigabitEthernet0/0/0]q [r1]interface LoopBack 0…

【Foxmail】客户端发送邮件错误:SSL Recv :服务器断开连接, errorCode: 6

Foxmail客户端发送邮件提示&#xff1a;SSL Recv :服务器断开连接, errorCode: 6 错误代码 处理方式&#xff1a; 去邮箱生成新的16位授权码&#xff0c;输入到 密码框 内即可。 注&#xff1a;一旦开通授权码&#xff0c;在Foxmail验证时 密码框 里输入的就是 授权码

Ddosify 作为压测工具的使用指南

文章目录 1. 写在最前面1.1 Kubernetes 监控1.2 Performance Testing 2. 命令行安装 & 使用2.1 安装2.2 使用2.2.1 默认的例子2.2.2 定制的例子 3. Dashboard 安装 & 使用3.1 安装3.2 使用3.2.1 简单使用3.2.3 依赖的服务介绍 4. 碎碎念5. 参考资料 1. 写在最前面 由于…

【单例模式】保证线程安全实现单例模式

&#x1f4c4;前言&#xff1a;本文是对经典设计模式之一——单例模式的介绍并讨论单例模式的具体实现方法。 文章目录 一. 什么是单例模式二. 实现单例模式1. 饿汉式2. 懒汉式2.1 懒汉式实现单例模式的优化&#xff08;一&#xff09;2.2 懒汉式实现单例模式的优化&#xff08…

EI论文复现:考虑冷热运行特性的综合能源系统多时间尺度优化调度程序代码!

适用平台/参考文献&#xff1a;MatlabYalmipCplex&#xff1b; 参考文献&#xff1a;电力系统自动化《含冰蓄冷空调的冷热电联供型微网多时间尺度优化调度》 提出考虑冷热特性的综合能源系统多时间尺度优化调度模型&#xff0c;日前计划中通过多场景描述可再生能源的不确定性…

表白墙网站PHP源码,支持封装成APP

源码介绍 PHP表白墙网站源码&#xff0c;适用于校园内或校区间使用&#xff0c;同时支持封装成APP。告别使用QQ空间的表白墙。 简单安装&#xff0c;只需PHP版本5.6以上即可。 通过上传程序进行安装&#xff0c;并设置账号密码&#xff0c;登录后台后切换模板&#xff0c;适配…

牛客30道题解析精修版

1.异常处理 都是Throwable的子类&#xff1a; ① Exception&#xff08;异常&#xff09;:是程序本身可以处理的异常。 ② Error&#xff08;错误&#xff09;: 是程序无法处理的错误。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时&#xff0c;一般不需要…

《统计学习方法:李航》笔记 从原理到实现(基于python)-- 第3章 k邻近邻法

文章目录 第3章 k邻近邻法3.1 k近邻算法3.2 k近邻模型3.2.1 模型3.2.2 距离度量3.2.3 k值的选择3.2.4 分类决策规则 3.3 k近邻法的实现&#xff1a;kd树3.3.1 构造kd树3.3.2 搜索kd树 算法实现课本例3.1iris数据集scikit-learn实例kd树:构造平衡kd树算法例3.2 《统计学习方法&a…

把Windows系统装进U盘,到哪都有属于你自己的电脑系统

前言 自从接触到WinPE启动盘之后&#xff0c;小白就突然萌生了一个想法&#xff1a;为啥不能把完整的Windows放进U盘呢&#xff1f; 实际上Windows是可以安装进U盘的&#xff0c;外出的时候带上&#xff0c;只需要有台正常开机的电脑就可以使用属于自己的系统。 这个是早已经…

一分钟教你搭建《幻兽帕鲁》服务器

幻兽帕鲁是一款由Pocketpair开发的开放世界生存游戏&#xff0c;融合了多种玩法的游戏&#xff0c;其独特的题材和画风吸引了很多玩家&#xff0c;越来越多的玩家开始尝试自己搭建服务器&#xff0c;享受更加自由的游戏体验。本文将为大家详细介绍如何从零开始搭建《幻兽帕鲁》…

「 网络安全常用术语解读 」杀链Kill Chain详解

1. 简介 早在2009年&#xff0c;Lockheed Martin公司就提出了杀链(Kill Chain)理论&#xff0c;现在也称之为攻击者杀链(Attacker Kill Chain)。杀链其实就是攻击者进行网络攻击时所采取的步骤。杀链模型包括7个步骤&#xff1a;1侦察 -> 2武器化 -> 3交付 -> 4利用 …

java程序判等问题

注意 equals 和 的区别 对基本类型&#xff0c;比如 int、long&#xff0c;进行判等&#xff0c;只能使用 &#xff0c;比较的是直接值。因为基本类型的值就是其数值。对引用类型&#xff0c;比如 Integer、Long 和 String&#xff0c;进行判等&#xff0c;需要使用 equals 进…

【立创EDA-PCB设计基础】6.布线铺铜实战及细节详解

前言&#xff1a;本文进行布线铺铜实战及详解布线铺铜的细节 在本专栏中【立创EDA-PCB设计基础】前面完成了布线铺铜前的设计规则的设置&#xff0c;接下来进行布线 布局原则是模块化布局&#xff08;优先布局好确定位置的器件&#xff0c;例如排针、接口、主控芯片&#xff…

Sulfo Cy3 hydrazide,磺化-Cy3-酰肼,可用于与生物分子的羰基衍生物偶联

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;Sulfo-Cyanine3-hydrazide&#xff0c;Sulfo Cy3 hydrazide&#xff0c;Sulfo Cyanine3 HZ&#xff0c;磺化 Cy3 酰肼&#xff0c;磺化-Cy3-酰肼 一、基本信息 产品简介&#xff1a;Sulfo-Cyanine3-hydrazide能够与…

远程连接银河麒麟

目录 一、防火墙服务 二、安装SSH服务 1.验证SSH服务是否安装 2.安装SSH服务 三、启动SSH服务 四、远程连接 1.切换登录用户 2.查看IP地址 3.FinalShell连接 4.切换root用户 前言: 本篇主要讲述在Win10系统中通过FinalShell远程连接银河麒麟桌面操作系统V10 一、防火…