这里我对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
=
trueserver.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()
  。