Spring Boot开发Spring Security

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