1. 程式人生 > 其它 >Spring Security 快速上手

Spring Security 快速上手

Spring Security 框架簡介

Spring Security 說明

  • Spring Security 基於 Spring 框架,提供了一套 Web 應用安全性的完整解決方案
    1. 關於安全方面的兩個主要區域是“認證”和“授權”(或者訪問控制),一般來說,Web 應用的安全性包括使用者認證(Authentication)和使用者授權(Authorization)兩個部分,這兩點也是 Spring Security 重要核心功能。
      1. 使用者認證:驗證某個使用者是否為系統中的合法主體,也就是說使用者能否訪問該系統。使用者認證一般要求使用者提供使用者名稱和密碼。系統通過校驗使用者名稱和密碼來完成認證過程。通俗點說就是系統認為使用者是否能登入。
      2. 使用者授權:驗證某個使用者是否有許可權執行某個操作。在一個系統中,不同使用者所具有的許可權是不同的。比如對一個檔案來說,有的使用者只能進行讀取,而有的使用者可以進行修改。一般來說,系統會為不同的使用者分配不同的角色,而每個角色則對應一系列的許可權。通俗點講就是系統判斷使用者是否有許可權去做某些事情。

Spring Security 官網

Spring Security 特點

  1. 和 Spring 無縫整合
  2. 重量級、全面的許可權控制
  3. 專門為 Web 開發而設計
    1. 舊版本不能脫離 Web 環境使用。
    2. 新版本對整個框架進行了分層抽取,分成了核心模組和 Web 模組。單獨引入核心模組就可以脫離 Web 環境。

Spring Security 對比 Shiro

Shiro 簡介

  • Shrio 是Apache 旗下的輕量級許可權控制框架

    特點:

    1. 輕量級。Shiro 主張的理念是把複雜的事情變簡單。針對對效能有更高要求的網際網路應用有更好表現。
    2. 通用性:
      1. 好處:不侷限於 Web 環境,可以脫離 Web 環境使用。
      2. 缺陷:在 Web 環境下一些特定的需求需要手動編寫程式碼定製。

總結

Spring Security 是 Spring 家族中的一個安全管理框架,實際上,在 Spring Boot 出現之前,Spring Security 就已經發展了多年了,但是使用的並不多,安全管理這個領域,一直是 Shiro 的天下。

相對於 Shiro,在 SSM 中整合 Spring Security 都是比較麻煩的操作,所以,SpringSecurity 雖然功能比 Shiro 強大,但是使用反而沒有 Shiro 多(Shiro 雖然功能沒有Spring Security 多,但是對於大部分專案而言,Shiro 也夠用了)。

自從有了 Spring Boot 之後,Spring Boot 對於 Spring Security 提供了自動化配置方案,可以使用更少的配置來使用 Spring Security。

因此,一般來說,常見的安全管理技術棧的組合是這樣的:

• SSM + Shiro
• Spring Boot/Spring Cloud + Spring Security

以上只是一個推薦的組合而已,如果單純從技術上來說,無論怎麼組合,都是可以執行的。

Spring Security 框架快速入門

  1. 搭建spring boot 專案,並引入專案依賴

     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 新建Controller,並配置訪問方法

    package org.taoguoguo.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author taoGuoGuo
     * @description HelloController
     * @website https://www.cnblogs.com/doondo
     * @create 2021-06-11 14:16
     */
    @RestController
    @RequestMapping("/test")
    public class HelloController {
    
        @GetMapping("hello")
        public String hello(){
            return "hello security";
        }
    }
    
  3. 訪問對應的方法,我們發現跳轉至了預設的Spring Security 登入頁面,使用者名稱預設為 user , 密碼為 控制檯列印 password

    Using generated security password: 22f1f943-b70b-43e9-bf56-adf37851a9e7

Spring Security 使用者名稱密碼配置

剛剛我們的密碼是在控制檯,Spring Security 內建實現的密碼登入,那實際工作中我們怎麼自己指定使用者名稱密碼呢?

常見的方式有三種:

  1. 通過配置檔案指定
  2. 通過配置類指定
  3. 通過實現 UserDetailService 介面實現 資料庫查詢

通過配置檔案指定

在 application.properties 中 進行配置

#第一種方式:通過配置檔案配置Spring Security使用者名稱密碼
spring.security.user.name=taoguoguo
spring.security.user.password=taoguoguo

通過配置類實現 配置使用者名稱密碼

新建 SecurityConfig 並 實現 WebSecurityConfigurerAdapter 介面卡實現配置重寫

package org.taoguoguo.config;

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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author taoGuoGuo
 * @description SecurityConfig
 * @website https://www.cnblogs.com/doondo
 * @create 2021-06-11 14:55
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //如果缺少密碼解析 登陸後會返回當前登陸頁面 並報 id 為 null 的錯誤
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //方式二:通過配置類設定登入使用者名稱和密碼,通過auth設定使用者名稱密碼
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("taoguoguo");
        auth.inMemoryAuthentication().withUser("taoguoguo").password(password).roles("admin");
    }
}

通過實現 UserDetailService 自定義實現

自定義實現類配置:

  1. 建立配置類,設定使用哪個userDetailService 實現類

    package org.taoguoguo.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.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    /**
     * @author taoGuoGuo
     * @description SecurityConfig
     * @website https://www.cnblogs.com/doondo
     * @create 2021-06-11 15:14
     * SpringSecurity 自定義配置
     */
    @Configuration
    public class SecurityConfig  extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //設定自定義的資料庫實現及密碼解析器
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    
        @Bean
        PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    
    
  2. 編寫實現類,返回User物件,User物件有使用者名稱密碼和操作許可權

    package org.taoguoguo.service;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * @author taoGuoGuo
     * @description MyUserDetailService
     * @website https://www.cnblogs.com/doondo
     * @create 2021-06-11 15:18
     */
    @Service("userDetailsService")
    public class MyUserDetailService implements UserDetailsService {
    
        /**
         * 自定義實現類方式查詢使用者名稱密碼
         * @param s
         * @return
         * @throws UsernameNotFoundException
         */
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            //密碼
            String password = passwordEncoder.encode("taoguoguo");
            //許可權
            List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
            return new User("taoguoguo", password, auths);
        }
    }
    

資料庫查詢認證

  1. 資料庫建立 users表

    CREATE TABLE `users` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `username` varchar(20) COLLATE utf8mb4_general_ci NOT NULL,
      `password` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `username` (`username`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
    
  2. 專案新增依賴 採用 mybatis-plus

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.5.1</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>org.taoguoguo</groupId>
        <artifactId>spring-security</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>spring-security</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.3</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    
  3. 增加資料庫引數配置

    server.port=8111
    
    #資料庫配置
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/security_db?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=root
    
  4. 建立Users實體

    package org.taoguoguo.entity;
    
    import lombok.Data;
    
    /**
     * @author taoGuoGuo
     * @description Users
     * @website https://www.cnblogs.com/doondo
     * @create 2021-06-11 15:51
     */
    @Data
    public class Users {
        private Integer id;
        private String username;
        private String password;
    }
    
    
  5. 建立UsersMapper

    package org.taoguoguo.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.springframework.stereotype.Repository;
    import org.taoguoguo.entity.Users;
    
    /**
     * @author taoGuoGuo
     * @description UsersMapper
     * @website https://www.cnblogs.com/doondo
     * @create 2021-06-11 15:53
     */
    @Repository
    public interface UsersMapper extends BaseMapper<Users> {
    }
    
    
  6. 修改自定義配置實現類 MyUserDetailService 進行資料庫查詢

    package org.taoguoguo.service;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    import org.taoguoguo.entity.Users;
    import org.taoguoguo.mapper.UsersMapper;
    import java.util.List;
    
    /**
     * @author taoGuoGuo
     * @description MyUserDetailService
     * @website https://www.cnblogs.com/doondo
     * @create 2021-06-11 15:18
     */
    @Service("userDetailsService")
    public class MyUserDetailService implements UserDetailsService {
    
        @Autowired
        private UsersMapper usersMapper;
        /**
         * 自定義實現類方式查詢使用者名稱密碼
         * @param username
         * @return
         * @throws UsernameNotFoundException
         */
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //呼叫usersMapper方法,根據使用者名稱查詢資料庫
            QueryWrapper<Users> wrapper = new QueryWrapper<>();
            wrapper.eq("username", username);
            Users users = usersMapper.selectOne(wrapper);
            if(users == null){  //資料庫沒有該使用者名稱 認證失敗
                throw new UsernameNotFoundException("使用者名稱或密碼不存在!");
            }
            //許可權
            List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
            return new User(users.getUsername(), users.getPassword(), auths);
        }
    }
    
    
  7. 在Spring Boot 配置啟動類中進行Mapper註解掃描配置 注入Mapper Bean

    package org.taoguoguo;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan("org.taoguoguo.mapper")
    public class SpringSecurityApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringSecurityApplication.class, args);
        }
    
    }
    

Spring Security自定義登入頁面 及 認證放行

通過重寫 webSecurityConfigureAdapter 的 configure(HttpSecurity http) 方法進行配置

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()    //自定義自己編寫登入頁面
                .loginPage("/login.html")           //登入頁面設定
                .loginProcessingUrl("/user/login")  //登入訪問路徑
                .defaultSuccessUrl("/test/index")   //登入成功後,跳轉路徑
                .permitAll()
                .and().authorizeRequests()  //定義請求路徑訪問規則
                .antMatchers("/","/test/hello","/user/login").permitAll() //設定不需要認證可以直接訪問的路徑
                .anyRequest().authenticated()   //除此之外任何請求都需要認證
                .and().csrf().disable();    //關閉csrf防護
    }

基於角色或許可權的進行訪問控制

  • hasAuthority 方法,主要針對某一個許可權進行控制訪問

    如果當前的主體具有指定的許可權,則返回 true,否則返回 false

    修改配置類,增加.antMatchers("/test/index").hasAuthority("admin") 具備admin許可權 才能訪問 /test/index路徑

    @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()    //自定義自己編寫登入頁面
                    .loginPage("/login.html")           //登入頁面設定
                    .loginProcessingUrl("/user/login")  //登入訪問路徑
                    .defaultSuccessUrl("/test/index")   //登入成功後,跳轉路徑
                    .permitAll()
                    .and().authorizeRequests()  //定義請求路徑訪問規則
                    .antMatchers("/","/test/hello","/user/login").permitAll() //設定不需要認證可以直接訪問的路徑
                    .antMatchers("/test/index").hasAuthority("admin")   //具備admin許可權才能訪問 /test/index資源
                    .anyRequest().authenticated()   //除此之外任何請求都需要認證
                    .and().csrf().disable();    //關閉csrf防護
        }
    

    那這個admin的許可權是在哪裡指定的呢?其實在之前我們已經提到過,在自定義的登入邏輯中,我們會放置使用者所具備的許可權資訊

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

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //呼叫usersMapper方法,根據使用者名稱查詢資料庫
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        Users users = usersMapper.selectOne(wrapper);
        if(users == null){  //資料庫沒有該使用者名稱 認證失敗
            throw new UsernameNotFoundException("使用者名稱或密碼不存在!");
        }
        //許可權
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User(users.getUsername(), users.getPassword(), auths);
    }
    
  • hasAnyAuthority 方法

    如果當前的主體有任何提供的角色(給定的作為一個逗號分隔的字串列表)的話,返回true

    修改配置類,增加.antMatchers("/test/index").hasAnyAuthority("admin,manager") 具備admin 或 manager其中任一許可權 就能訪問 /test/index路徑

     @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()    //自定義自己編寫登入頁面
            .loginPage("/login.html")           //登入頁面設定
            .loginProcessingUrl("/user/login")  //登入訪問路徑
            .defaultSuccessUrl("/test/index")   //登入成功後,跳轉路徑
            .permitAll()
            .and().authorizeRequests()  //定義請求路徑訪問規則
            .antMatchers("/","/test/hello","/user/login").permitAll() //設定不需要認證可以直接訪問的路徑
            .antMatchers("/test/index").hasAnyAuthority("admin,manager")
            .anyRequest().authenticated()   //除此之外任何請求都需要認證
            .and().csrf().disable();    //關閉csrf防護
    }
    
  • hasRole 如果使用者具備給定角色就允許訪問,否則出現 403 如果當前主體具有指定的角色,則返回 true。

    檢視原始碼用法

    // hasRole 取出角色相關資訊 access 判斷是否能夠訪問
    public ExpressionInterceptUrlRegistry hasRole(String role) {
        return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
    }
    
    // 為role 底層拼接 ROLE_ 所以 我們在自定義登入邏輯 賦予許可權時 要拼接 ROLE_
    private static String hasRole(String role) {
        Assert.notNull(role, "role cannot be null");
        Assert.isTrue(!role.startsWith("ROLE_"),
                      () -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
        return "hasRole('ROLE_" + role + "')";
    }
    
    

    修改配置類,配置使用者具備 deptManager才能訪問 /test/index資源

     @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()    //自定義自己編寫登入頁面
            .loginPage("/login.html")           //登入頁面設定
            .loginProcessingUrl("/user/login")  //登入訪問路徑
            .defaultSuccessUrl("/test/index")   //登入成功後,跳轉路徑
            .permitAll()
            .and().authorizeRequests()  //定義請求路徑訪問規則
            .antMatchers("/","/test/hello","/user/login").permitAll() //設定不需要認證可以直接訪問的路徑
            .antMatchers("/test/index").hasRole("deptManager")
            .anyRequest().authenticated()   //除此之外任何請求都需要認證
            .and().csrf().disable();    //關閉csrf防護
    }
    

    給使用者配置deptManager角色許可權

     @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //呼叫usersMapper方法,根據使用者名稱查詢資料庫
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        Users users = usersMapper.selectOne(wrapper);
        if(users == null){  //資料庫沒有該使用者名稱 認證失敗
            throw new UsernameNotFoundException("使用者名稱或密碼不存在!");
        }
        //許可權
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("manager,ROLE_deptManager");
        return new User(users.getUsername(), users.getPassword(), auths);
    }
    
  • hasAnyRole 表示使用者具備任何一個條件都可以訪問

    用法和配置和 hasRole 一致,只不過在配置類中配置多個角色方可訪問資源路徑,同時給使用者角色資訊時,給一個或多個即可

Spring Security 自定義403 無許可權訪問頁面

  1. 我們在專案的靜態資原始檔夾 static 下 新建 unauth.html 自定義的403頁面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>自定義403頁面</title>
    </head>
    <body>
    自定義403頁面!
    </body>
    </html>
    
  2. 修改訪問配置類

    http.exceptionHandling().accessDeniedPage("/unauth");
    

Spring Security 註解使用

@Secured 判斷是否具有角色

  1. 開啟Spring Security註解功能 @EnableGlobalMethodSecurity(securedEnabled = true)

    package org.taoguoguo;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    
    @SpringBootApplication
    @MapperScan("org.taoguoguo.mapper")
    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SpringSecurityApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringSecurityApplication.class, args);
        }
    
    }
    
    
  2. 編寫需要 Secured 註解指定角色訪問的控制器

    @GetMapping("testSecured")
    @Secured({"ROLE_sale","ROLE_admin"})
    public String testSecured(){
        return "hello Secured";
    }
    
  3. 在自定義登入邏輯中,為使用者分配對應的角色資訊

    List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("manager,ROLE_sale");

@PreAuthorize 進入方法前的許可權驗證

  1. 開啟註解功能 @EnableGlobalMethodSecurity(prePostEnabled = true)

    package org.taoguoguo;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    
    @SpringBootApplication
    @MapperScan("org.taoguoguo.mapper")
    @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
    public class SpringSecurityApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringSecurityApplication.class, args);
        }
    
    }
    
    
  2. 編寫需要 preAuthorize 需要許可權驗證的控制器

    @RequestMapping("/preAuthorize")
    @PreAuthorize("hasAuthority('menu:system')")
    public String preAuthorize(){
        return "Hello preAuthorize";
    }
    
  3. 在自定義登入邏輯中,為使用者分配對應的角色資訊

    List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("manager,menu:system,menu:create");

@PostAuthorize 在方法執行後再進行許可權驗證,適合驗證帶有返回值的許可權

用法和 preAuthorize 基本一致,比較簡單,這邊就不詳細說明了。重點明白什麼場景下使用即可

@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;
}

@PreFilter 進入控制器之前對資料進行過濾

留下傳入引數 id 能對 2 整除的引數資料

@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;
}

Spring Security 實現 RememberMe

使用者流程及框架實現原理

具體實現方式

  1. 建立表,這個表也可以由程式自動建立

    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;
    
  2. 新增資料庫配置檔案

    #資料庫配置
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/security_db?serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=root
    
  3. 修改配置類 SecurityConfig 中 注入資料來源及配置資料庫操作物件

    //注入資料來源
    @Autowired
    private DataSource dataSource;
    
    //注入資料來源 配置操作資料庫物件
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //jdbcTokenRepository.setCreateTableOnStartup(true);  //配置此配置則會自動幫我們建表
        return jdbcTokenRepository;
    }
    
  4. 開啟 Remeber me 功能

    //開啟記住我功能
    http.rememberMe().tokenRepository(persistentTokenRepository())
        .tokenValiditySeconds(60*15)    //有效時長 15分鐘
        .userDetailsService(userDetailsService);
    
  5. login.html 頁面增加記住我 功能標籤 【注意:name 屬性值必須位 remember-me.不能改為其他值】

    <form action="/user/login" method="post">
        使用者名稱:<input type="text" name="username"><br/>
        密 碼:<input type="password" name="password"><br/>
        <input type="checkbox" name="remember-me" title="記住密碼">自動登入<br/>
        <input type="submit" value="login">
    </form>
    

最後附上完整的配置類程式碼

package org.taoguoguo.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.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * @author taoGuoGuo
 * @description SecurityConfig
 * @website https://www.cnblogs.com/doondo
 * @create 2021-06-11 15:14
 * SpringSecurity 自定義配置
 */
@Configuration
public class SecurityConfig  extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    //注入資料來源
    @Autowired
    private DataSource dataSource;

    //注入資料來源 配置操作資料庫物件
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //jdbcTokenRepository.setCreateTableOnStartup(true);  //配置此配置則會自動幫我們建表
        return jdbcTokenRepository;
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //設定自定義的資料庫實現及密碼解析器
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //表單認證
        http.formLogin()    //自定義自己編寫登入頁面
                .loginPage("/login.html")           //登入頁面設定
                .loginProcessingUrl("/user/login")  //登入訪問路徑
                .defaultSuccessUrl("/sys/loginSuccess")   //登入成功後,跳轉路徑
                .permitAll()
                .and().authorizeRequests()  //定義請求路徑訪問規則
                .antMatchers("/","/test/hello","/user/login").permitAll() //設定不需要認證可以直接訪問的路徑
                //1 hasAuthority方法 具備admin許可權才能訪問 /test/index資源
                // .antMatchers("/test/index").hasAuthority("admin")
                //2 hasAnyAuthority方法 具備admin,manager其中任一許可權 才能訪問/test/index資源
                // .antMatchers("/test/index").hasAnyAuthority("admin,manager")
                .antMatchers("/test/index").hasRole("manager")
                .anyRequest().authenticated();   //除此之外任何請求都需要認證

        //開啟記住我功能
        http.rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60*15)    //有效時長 15分鐘
                .userDetailsService(userDetailsService);

        //關閉csrf防護
        http.csrf().disable();
        //使用者登出配置
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/sys/logout").permitAll();
        //自定義403頁面
        http.exceptionHandling().accessDeniedPage("/sys/unauth403");
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}