Spring Security基本配置(登录,注销,权限,记住我,注解)

Spring Security

简介

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问
该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认
证过程。通俗点说就是系统认为用户是否能登录

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户
所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以
进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的
权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

SpringSecurity 特点:

  • 和 Spring 无缝整合。
  • 全面的权限控制。
  • 专门为 Web 开发而设计。
  • 旧版本不能脱离 Web 环境使用。
  • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独
    引入核心模块就可以脱离 Web 环境。
  • 重量级。

Shiro

Apache 旗下的轻量级权限控制框架。
特点:

  • 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求
    的互联网应用有更好表现。
  • 通用性。
  • 好处:不局限于 Web 环境,可以脱离 Web 环境使用。
  • 缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制

认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

SpringSecurity 入门案例

创建一个springboot项目,加入web和security的启动器

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

controller

package com.blb.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    
    

    @GetMapping("/hello")
    public String hello(){
    
    
        return "hello";
    }
}

访问

http://localhost:8080/hello

在这里插入图片描述
默认的用户名:user
密码在项目启动的时候在控制台会打印,注意每次启动的时候密码都回发生变化!
在这里插入图片描述
在这里插入图片描述
输入玩账户密码后才能进入返回的页面

SpringSecurity 本质是一个过滤器链:
从启动是可以获取到过滤器链:

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil
ter
org.springframework.security.web.context.SecurityContextPersistenceFilter 
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter 
org.springframework.security.web.session.SessionManagementFilter 
org.springframework.security.web.access.ExceptionTranslationFilter 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor

配置

设置用户名和密码

方式一 通过配置文件

spring:
  security:
    user:
      name: dyk
      password: 123456

方式二 通过配置类

@Configuration
public class SecurityConfig1 extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    BCryptPasswordEncoder password(){
    
    
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
        String password=passwordEncoder.encode("123456");
        auth.inMemoryAuthentication().withUser("dyk123").password(password).roles("admin");
    }
}

方式三 自定义编写实现类

1.创建配置类,使用UserDetailsService实现类
2.编写实现类,返回User对象,User对象有用户名和密码和操作权限

创建MyUserDetailsService实现UserDetailsService 返回User对象


@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    
    
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
    
        List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User("cb123",new BCryptPasswordEncoder().encode("123"),auths);
    }
}

以后用户名密码权限都是通过查询数据库得到的这里我是用集合模拟的假数据

配置类注入MyUserDetailsService



@Configuration
public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    BCryptPasswordEncoder password(){
    
    
        return new BCryptPasswordEncoder();
    }
   @Autowired
   private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
}

返回值 UserDetails

这个类是系统默认的用户“主体”

public interface UserDetails extends Serializable {
    
    
// 表示获取登录用户所有权限
Collection<? extends GrantedAuthority> getAuthorities();
// 表示获取密码
String getPassword();
// 表示获取用户名
String getUsername();
// 表示判断账户是否过期
boolean isAccountNonExpired();
// 表示判断账户是否被锁定
boolean isAccountNonLocked();
// 表示凭证{密码}是否过期
boolean isCredentialsNonExpired();
// 表示当前用户是否可用
boolean isEnabled();
}

我们只需要使用 User 这个实体类即可

public class User implements UserDetails, CredentialsContainer {
    
    
    private static final long serialVersionUID = 550L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
    
    
        this(username, password, true, true, true, true, authorities);
    }

通过查询数据库设置账户密码权限

依赖

这里新引入了mysql,mybatisplus,lombok依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db3?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
  thymeleaf:
    cache: false
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

创建数据库

CREATE TABLE users(

   id INT PRIMARY KEY ,
   username VARCHAR(20),
   PASSWORD VARCHAR(20)
)CHARSET=utf8

实体类Users

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Users {
    
    
    private Integer id;
    private String username;
    private String password;
}

创建接口继承MybatisPlus的BaseMapper

@Repository
@Mapper
public interface UsersMapper extends BaseMapper<Users> {
    
    
}

可以使用@Mapper或者在启动类上加入MapperScan

查询数据库

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    
    
    @Autowired
    private UsersMapper usersMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        //调用userMapper方法,根据用户查询数据库
        QueryWrapper queryWrapper=new QueryWrapper();
        queryWrapper.eq("username",username);
        Users users = usersMapper.selectOne(queryWrapper);

        //判断
        if(users==null){
    
    
            throw new UsernameNotFoundException("用户名不存在");
        }

        List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
    }
}

自定义用户登录页面

在配置类中重写protected void configure(HttpSecurity http) 方法



@Configuration
public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    BCryptPasswordEncoder password(){
    
    
        return new BCryptPasswordEncoder();
    }
   @Autowired
   private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        .defaultSuccessUrl("/index").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证
                .anyRequest().authenticated() //其他所有请求都需要先验证才能访问
                .and().csrf().disable();//关闭csrf防护
    }
}

在static目录下新建login.html

注意表单提交地址应该与loginProcessingUrl("/user/login")//登录访问的路径一致

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
     <form action="/user/login" method="post">
         用户名:<input type="text" name="username"><br>
         密码:<input type="text" name="password"><br>
         <input type="submit" value="登录">
     </form>
</body>
</html>

注意 表单里面的name标签必须是username和password
原因:
在执行登录的时候会走一个过滤器 UsernamePasswordAuthenticationFilter
也可以修改

@Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()//自定义自己编写的登录页面
        
         .usernameParameter("UserName")//自定义入参
         .passwordParameter("PassWord")


        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        .successForwardUrl("/suc")
         .failureForwardUrl("/err").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/user/login","/success.html","/error.html").permitAll()//设置那些路径可以直接访问,不需要认证

                .anyRequest().authenticated() //其他请求需要认证
                .and().csrf().disable();//关闭csrf防护
    }

修改后注意表单的两个name应该为UserName,PassWord

controller

@RestController
public class TestController {
    
    

    @GetMapping("/hello")
    public String hello(){
    
    
        return "hello";
    }

    @RequestMapping("/index")
    public String index(){
    
    
        return "index";
    }

}

在这里插入图片描述
可以发先由于设置了

antMatchers("/","/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证

现在不需要登录就能访问/hello了

表单的提交地址要和配置类中的一致

<form action="/user/login" method="post">

http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        .defaultSuccessUrl("/index").permitAll()//登录成功后的跳转路径

并且登录成功后会自动访问controller里的/index路径

自定义登录成功处理器

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
    
    private String url;

    public MyAuthenticationSuccessHandler(String url) {
    
    
        this.url = url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
    
    
         httpServletResponse.sendRedirect(url);

    }
}

登录成功后跳转到百度
注意要把上面successForwardUrl注释掉

 @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        //.successForwardUrl("/suc")
        
          .successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
          
         .failureForwardUrl("/err").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/user/login","/success.html","/error.html").permitAll()//设置那些路径可以直接访问,不需要认证

                .anyRequest().authenticated() //其他请求需要认证
                .and().csrf().disable();//关闭csrf防护
    }

同理自定义失败处理器只需要实现AuthenticationFailureHandler重写方法即可

基于角色或权限进行访问

我的理解是角色和权限,角色其实是权限的集合

anyRequest()

表示匹配所有请求,一般情况下都会使用设置全部内容需要进行认证

.anyRequest().authenticated() //其他请求需要认证

antMatchers()

方法定义如下:

public CantMatchers(String...antPatterns)

参数不定向参数,每个参数是一个ant表达式,用于匹配url规则

规则如下:

  • ?·匹配一个字符
  • *·匹配0个或多个字符
  • **·匹配0个或多个目录

在实际项目中放行静态资源

放行js,css文件下所有静态资源

.antMatchers("/js/**","/css/**").permitAll()//设置那些路径可以直接访问,不需要认证

另一种方式是只有.js文件都放行

.antMatchers("/**/*.js"").permitAll()//设置那些路径可以直接访问,不需要认证

hasAuthority()

如果当前的主体具有指定的权限,则返回 true,否则返回 false

.antMatchers("/insert").hasAnyAuthority("admin")

hasAnyAuthority

用户具备其中某一个权限,就允许访问

.antMatchers("/insert").hasAnyAuthority("admin","user")

在配置类设置当前访问路径需要有那些权限

注意:.anyRequest().authenticated() //所有请求需要认证这个应该放在最后面,是从上往下执行的,如果放在前面,请求可能就被拦截了

@Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        .defaultSuccessUrl("/index").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证
                
                
                //当前登录用户,只有具有admin权限才可以访问这个路径
                .antMatchers("/").hasAuthority("admin")


                .anyRequest().authenticated() //所有请求需要认证
                .and().csrf().disable();//关闭csrf防护
    }

在UserDetailsService,把返回user对象设置权限

当这边设置的权限是role而不是admin时

 List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);

在这里插入图片描述
当把当前登录用户权限设置为admin时即可访问 /

List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User(users.getUsername(),
        new BCryptPasswordEncoder().encode(users.getPassword()),auths);

在这里插入图片描述

hasAnyAuthority

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true.

@Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        .defaultSuccessUrl("/index").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证
                //当前登录用户,只有具有admin权限才可以访问这个路径
                .antMatchers("/").hasAuthority("admin")


                //当前登录用户,只要具有admin或manager其中一个即可访问这个路径
                .antMatchers("/hello").hasAnyAuthority("admin,manager")


                .anyRequest().authenticated() //其他请求需要认证
                .and().csrf().disable();//关闭csrf防护
    }
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User(users.getUsername(),
        new BCryptPasswordEncoder().encode(users.getPassword()),auths);

和上面那个方法效果几乎一样,这里就不给出截图了

hasRole

如果用户具备给定角色就允许访问,否则出现 403。
如果当前主体具有指定的角色,则返回 true

@Configuration
public class SecurityConfig3 extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    BCryptPasswordEncoder password(){
    
    
        return new BCryptPasswordEncoder();
    }
   @Autowired
   private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        .defaultSuccessUrl("/index").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证
                //当前登录用户,只有具有teacher角色才可以访问这个路径
                .antMatchers("/").hasRole("teacher")

                .anyRequest().authenticated() //其他请求需要认证
                .and().csrf().disable();//关闭csrf防护
    }
}

注意给用户添加角色要加上ROLE_

List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_teacher");

        return new User(users.getUsername(),
        new BCryptPasswordEncoder().encode(users.getPassword()),auths);

hasAnyRole

表示用户具备任何一个条件都可以访问。
给用户添加角色

基本作用和上面一样,这里就不做过多描述,区别就是只要具备其中一个角色就能访问该路径

自定义没有权限访问页面

@Configuration
public class SecurityConfig3 extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    BCryptPasswordEncoder password(){
    
    
        return new BCryptPasswordEncoder();
    }
   @Autowired
   private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        //配置自定义没有权限403跳转页面
        http.exceptionHandling().accessDeniedPage("/403.html");
        
        
        http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路径
        .defaultSuccessUrl("/index").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证
                //当前登录用户,只有具有teacher角色才可以访问这个路径
                .antMatchers("/").hasRole("teacher")

                .anyRequest().authenticated() //其他请求需要认证
                .and().csrf().disable();//关闭csrf防护
    }
}

403.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>403页面</title>
  <style>
    
    body {
      
      
      width: 100%;
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      family: font;
      background-image: linear-gradient(45deg, #f6d200 25%, #181617 25%, #181617 50%, #f6d200 50%, #f6d200 75%, #181617 75%, #181617 100%);
    }

    h1 {
      
      
      text-transform: uppercase;
      background: repeating-linear-gradient(
              45deg,
              #f6d200 ,
              #f6d200  10px,
              #181617  10px,
              #181617  20px
      );
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      /*animation: move 5s ease infinite;*/
      font-size: 384px;
      margin: 0;
      line-height: .7;
      position: relative;
    :before,
    :after{
      
      
       content: "Caution";
       background-color: #f6d200;
       color: #181617;
       border-radius: 10px;
       font-size: 35px;
       position: absolute;
       padding: 31px;
       text-transform: uppercase;
       font-weight: bold;
       -webkit-text-fill-color: #181617;
       left: 50%;
       top: 50%;
       transform: translate(-50%, -50%) rotate(20deg);
     }
    :before {
      
      
       content: "";
       padding: 70px 130px;
       background: repeating-linear-gradient(45deg, #f6d200, #f6d200 10px, #181617 10px, #181617 20px);
       box-shadow: 0px 0px 10px #181617;
     }
     span:before,
     span:after{
      
      
        content: "";
        width: 8px;
        height: 8px;
        background: #757575;
        color: #757575;
        border-radius: 50%;
        position: absolute;
        bottom: 0;
        margin: auto;
        top: 20%;
        z-index: 3;
        box-shadow: 0px 60px 0 0px;
      }
    span:before {
      
      
      left: 37%;
      transform: rotate(22deg);
      top: -44%;
    }
    span:after {
      
      
      right: 34%;
      transform: rotate(22deg);
      top: 3%;
    }
    }

  </style>
</head>
<body>
<h1 class="text"><span>403</span></h1>
</body>
</html>

注解的使用

@Secured

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
使用注解先要开启注解功能!

在启动类(或配置类)开启注解

@EnableGlobalMethodSecurity(securedEnabled = true)

在controller的方法上使用注解,设置角色

@RequestMapping("/insert")
    @Secured({
    
    "ROLE_sale","ROLE_manager"})
    public String insert()
    {
    
    
        return "insert";
    }

UserDetailsService设置用户角色

List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_manager");
        return new User(users.getUsername(),
        new BCryptPasswordEncoder().encode(users.getPassword()),auths);

在这里插入图片描述

@PreAuthorize

@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用
户的 roles/permissions 参数传到方法中

在启动类或者配置类先开启注解功能:

@EnableGlobalMethodSecurity(prePostEnabled = true)

在controller方法上添加注解

 @RequestMapping("/update")
    @PreAuthorize("hasAnyAuthority('admin')")
    //@PreAuthorize("hasAnyRole('ROLE_manager')")
    public String update()
    {
    
    
        return "update";
    }

UserDetailsService设置用户角色

List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_manager,admin");
        return new User(users.getUsername(),
        new BCryptPasswordEncoder().encode(users.getPassword()),auths);

@PostAuthorize

在启动类或者配置类先开启注解功能:

@EnableGlobalMethodSecurity(prePostEnabled = true)

@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限

在controller方法上添加注解

@RequestMapping("/delete")
    @PostAuthorize("hasAnyAuthority('teacher')")
    public String delete(){
    
    
        System.out.println("delete 已经执行");

        return "delete";
    }

UserDetailsService设置用户角色

List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_manager,admin");
        return new User(users.getUsername(),
        new BCryptPasswordEncoder().encode(users.getPassword()),auths);

可以一眼就看出来是没有权限访问该路径的
但是里面打印的内容却已经执行了

在这里插入图片描述

在这里插入图片描述

@PostFilter

@PostFilter :权限验证之后对数据进行过滤 留下用户名是 admin1 的数据表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素

对方法的返回数据过滤

@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){
    
    
 ArrayList<UserInfo> list = new ArrayList<>();
 list.add(new UserInfo(1l,"admin1","6666"));
 list.add(new UserInfo(2l,"admin2","888"));
return list;
}

只有username是admin1的才会返回

@PreFilter

@PreFilter: 进入控制器之前对数据进行过滤

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> 
list){
    
    
 list.forEach(t-> {
    
    
 System.out.println(t.getId()+"\t"+t.getUsername());
 });
return list;
}

只有id是偶数的才会传进去

用户注销

success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 <h1>登录成功</h1>
 <a href="/logout">退出</a>
</body>
</html>

在配置类中添加退出映射地址

//配置退出映射地址
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();

退出之后,是无法访问需要登录时才能访问的控制器

基于数据库的记住我

数据库表

CREATE TABLE `persistent_logins` (
 `username` varchar(64) NOT NULL,
 `series` varchar(64) NOT NULL,
 `token` varchar(64) NOT NULL,
 `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP,
 PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

添加数据库的配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
  thymeleaf:
    cache: false
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

编写配置类

要注入数据源和PersistentTokenRepository实现类

 //注入数据源
    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
    
    
        JdbcTokenRepositoryImpl jdbcTokenRepository=new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动创建数据库表
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
.and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60)//设置有效时长,以秒为单位
                .userDetailsService(userDetailsService)
package com.blb.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
public class SecurityConfig5 extends WebSecurityConfigurerAdapter {
    
    
    //注入数据源
    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
    
    
        JdbcTokenRepositoryImpl jdbcTokenRepository=new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动创建数据库表
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Bean
    BCryptPasswordEncoder password(){
    
    
        return new BCryptPasswordEncoder();
    }
   @Autowired
   private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        //配置自定义没有权限403跳转页面
        http.exceptionHandling().accessDeniedPage("/403.html");

        //配置退出映射地址
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();

        http.formLogin()//自定义自己编写的登录页面
        .loginPage("/login.html")//登录页面设置
        .loginProcessingUrl("/user/login")//登录访问的路
        .defaultSuccessUrl("/success.html").permitAll()//登录成功后的跳转路径
        .and().authorizeRequests()
                .antMatchers("/","/hello","/user/login").permitAll()//设置那些路径可以直接访问,不需要认证
//                //当前登录用户,只有具有teacher角色才可以访问这个路径

                .anyRequest().authenticated() //其他请求需要认证
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60)//设置有效时长,以秒为单位
                .userDetailsService(userDetailsService)


                .and().csrf().disable();//关闭csrf防护
    }
}

页面添加记住我复选框

记住我:<input type="checkbox"name="remember-me"title="记住密码"/><br/>

此处:name 属性值必须位 remember-me.不能改为其他值

结合thymeleaf

<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>
  • 整合包4(springsecurity4)——springboot版本2.0.9
  • 整合包5(springsecurity5)——springboot版本之后

导入命名空间

<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<!--登录注销-->
<div class="right menu">

    <!--如果未登录-->
    <div sec:authorize="!isAuthenticated()">
        <a class="item" th:href="@{/login}">
            <i class="address card icon"></i> 登录
        </a>
    </div>

    <!--如果已登录-->
    <div sec:authorize="isAuthenticated()">
        <a class="item">
            <i class="address card icon"></i>
            用户名:<span sec:authentication="principal.username"></span>
            角色:<span sec:authentication="principal.authorities"></span>
        </a>
    </div>

    <div sec:authorize="isAuthenticated()">
        <a class="item" th:href="@{/logout}">
            <i class="sign-out  icon"></i> 注销
        </a>
    </div>
</div>
<!--菜单根据用户的角色动态的实现-->
<div class="column"  sec:authorize="hasRole('vip1')">
    <div class="ui raised segment">
        <div class="ui">
            <div class="content">
                <h5 class="content">Level 1</h5>
                <hr>
                <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
            </div>
        </div>
    </div>
</div>

<div class="column"  sec:authorize="hasRole('vip2')">
    <div class="ui raised segment">
        <div class="ui">
            <div class="content">
                <h5 class="content">Level 2</h5>
                <hr>
                <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
            </div>
        </div>
    </div>
</div>

<div class="column"  sec:authorize="hasRole('vip3')">
    <div class="ui raised segment">
        <div class="ui">
            <div class="content">
                <h5 class="content">Level 3</h5>
                <hr>
                <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
            </div>
        </div>
    </div>
</div>

猜你喜欢

转载自blog.csdn.net/qq_44866153/article/details/118879454