天天看點

Spring Security 查詢資料庫(五)

目錄

一、概述

二、搭建自定義UserDetailsService

1. 添加mybatis-plus , 建立資料表,并添加資料

2. 建立資料庫表結構

3.  我們建立自定義 MyUserDetailsServiceImpl  

4. SecurityConfig 配置

5. LoginController 頁面跳轉方法

6. 異常處理資訊

三、前端頁面配置

1.  login.html 登入頁面

2.  main.html 首頁面

3.  error.html 錯誤頁面

四、驗證效果

五、RememberMe功能實作

六、總結

Spring Security 學習專欄

1. Spring Security 入門學習(一)

2. Spring Security 自定義認證管理器和講解 (二)

3. Spring Security 接口詳解 (三)

4. Spring Security 工作原理 (四)

5. Spring Security 查詢資料庫(五)

一、概述

下面開始實戰使用Spring Security 的執行個體查詢資料庫,依然依托上一篇的例子,并在此基礎上調整。

通過資料庫查詢,存儲使用者和角色實作安全認證

在上一篇的例子中(Spring Security 自定義認證管理器和講解),我們使用了記憶體使用者角色來示範登入認證。但是實際項目我們肯定是通過資料庫完成的。實際項目中,我們可能會有五張表:使用者表,角色表,使用者角色關聯表,權限表,角色權限關聯表。本例示範的意義在于:如果我們想在已有項目中增加Spring Security的話,就需要調整登入了。主要是自定義

UserDetailsService

,此外,可能還需要處理密碼的問題,因為Spring并不知道我們怎麼加密使用者登入密碼的。這時,我們可能需要自定義PasswordEncoder

(

Spring Security 接口詳解

)

二、搭建自定義UserDetailsService

1. 添加mybatis-plus , 建立資料表,并添加資料

繼續完善開篇的項目,現在給項目添加

mybatis-plus和資料庫驅動

,并使用MySQL資料庫。是以在POM檔案添加如下jar包

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
    
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>
       <!--Mysql資料庫驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
           

在 application.yml檔案中加入資料庫連接配接資訊:

spring:
  #資料庫連接配接配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://47.103.20.21:3307/zlp-mall?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: zxx1234xx
           

2. 建立資料庫表結構

主表:sys_user,sys_role,sys_permission 三張表都是多對多的關系

關聯關系表:sys_role_permission_relation, sys_user_role_relation

DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '使用者名',
  `password` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密碼',
  `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '使用者昵稱',
  `user_type` int(2) NOT NULL DEFAULT 1 COMMENT '使用者類型 (0:管理者;1:普通使用者)',
  `status` int(2) NOT NULL DEFAULT 0 COMMENT '狀态(0:正常,1:删除)',
  `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
  `create_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '建立使用者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改時間',
  `update_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '管理使用者表' ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名稱',
  `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '建立時間',
  `role_status` int(1) NULL DEFAULT 1 COMMENT '啟啟用狀态:0->啟用 ; 1->禁用',
  `sort` int(11) NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '背景使用者角色表' ROW_FORMAT = Dynamic;


DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `pid` bigint(20) NULL DEFAULT NULL COMMENT '父級權限id',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名稱',
  `permission_value` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '權限值',
  `icon` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '圖示',
  `type` int(1) NULL DEFAULT NULL COMMENT '權限類型:0->目錄;1->菜單;2->按鈕(接口綁定權限)',
  `data_type` int(1) NULL DEFAULT 0 COMMENT '權限資料類型:0->檢視全部;1->檢視自己資料權限',
  `url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '前端資源路徑',
  `permission_status` int(1) NOT NULL DEFAULT 1 COMMENT '啟用狀态;0->啟用;1->禁用',
  `sort` int(4) NULL DEFAULT NULL COMMENT '排序',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '建立時間',
  `create_user` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '建立使用者',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '建立時間',
  `update_user` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '建立使用者',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_pid`(`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 64 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '背景使用者權限表' ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `sys_user_role_relation`;
CREATE TABLE `sys_user_role_relation`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NULL DEFAULT NULL,
  `role_id` bigint(20) NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '建立時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '背景使用者和角色關系表' ROW_FORMAT = Dynamic;

DROP TABLE IF EXISTS `sys_role_permission_relation`;
CREATE TABLE `sys_role_permission_relation`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) NULL DEFAULT NULL,
  `permission_id` bigint(20) NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '建立時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 793 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '背景使用者角色和權限關系表' ROW_FORMAT = Dynamic;
           

3.  我們建立自定義 MyUserDetailsServiceImpl  

前面我們提到過,UserDetailsService,Spring Security在認證過程中需要查找使用者,會調用UserDetailsService的loadUserByUsername方法得到一個UserDetails,下面我們來實作他。代碼如下:

@Service("userDetailsService")
@Slf4j(topic = "MyUserDetailsServiceImpl")
public class MyUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        log.info("loadUserByUsername.req username={}",username);
        //  1. 查詢使用者
        User user = userService.loadUserByUsername(username);
        if (Objects.isNull(user)) {
            log.error("User [" + username + "] was not found in db");
            throw new UsernameNotFoundException("User [" + username + "] was not found in db");
        }
        // 密碼
        String password = user.getPassword();
        // 2. 設定權限角色資訊
        List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList
                ("ADMIN,ROLE_abc");
        return new org.springframework.security.core.userdetails.User(username,password, grantedAuthorities);

    }
}
           

4. SecurityConfig 配置

設定自定義的userDetailsService,密碼采用PasswordEncoder密碼解析器

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private MyAccessDeniedHandler accessDeniedHandler;

    /** 
     * 認證管理器配置
     * @param auth
     * @date: 2021/3/11 17:39
     * @return: void 
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // 設定自定義的userDetailsService
        auth.userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder());
    }

    /**
     * 密碼加密
     * @date: 2021/3/27 18:31
     * @return: org.springframework.security.crypto.password.PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

       // 配置請求位址的權限
        http.authorizeRequests()
                // 所有使用者可通路,不需要登入
                .antMatchers("/test/echo").permitAll()
                // 設定前端頁面
                .antMatchers("/login.html").permitAll()
                // 需要 ADMIN 權限
//                .antMatchers("/test/admin","/test/getUserInfo").hasAuthority("ADMIN")
                // 需要 ROLE_abc 角色,要去掉ROLE_字首,不然會報錯,SpringSecurity會自動拼接ROLE_字首
                .antMatchers("/test/admin","/test/getUserInfo").hasRole("abc")
                // 需要 NORMAL 權限
                .antMatchers("/test/normal","/test/getUserInfo").hasAuthority("NORMAL") // 需要 NORMAL 角色。
                // 任何請求,通路的使用者都需要經過認證
                .anyRequest().authenticated();

        //異常處理
        http.exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler);
       // 設定 Form 表單登陸
        http.formLogin()
                //自定義登入頁面
                .loginPage("/showLogin")
                //必須和表單送出的接口一樣,會去執行自定義登入邏輯
                .loginProcessingUrl("/login")
                //登入成功後跳轉頁面,POST請求
                .successForwardUrl("/toMain")
                //登入失敗後跳轉頁面,POST請求
                .failureForwardUrl("/toError")
                .permitAll();

        // 禁止跨域
        http.csrf().disable();
    }
}
           

5. LoginController 頁面跳轉方法

@Controller
public class LoginController {

    /**
     * 頁面跳轉
     * @return
     */
    @GetMapping("/showLogin")
    public String showLogin(){
        return "login.html";
    }


    /**
     * 成功後跳轉頁面
     */
    @PostMapping("/toMain")
    @PreAuthorize("hasRole('ROLE_abc')")
    public String toMain() {
        return "redirect:/main.html";
    }

    /**
     * 失敗後跳轉頁面
     * @return
     */
    @PostMapping("/toError")
    public String toError() {
        return "redirect:/error.html";
    }
}
           

6. 異常處理資訊

     自定義 MyAccessDeniedHandler 實作 AccessDeniedHandler 代碼如下

代碼沒有生效,也不知道什麼原因???

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {

	@Override
	public void handle(HttpServletRequest request,
					   HttpServletResponse response,
					   AccessDeniedException e) throws IOException{

		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json");
		response.getWriter().println(JSONUtil.parse(Result.forbidden(e.getMessage())));
		response.getWriter().flush();
	}
}
           

就改成全局捕獲異常代碼如下

/**
 * 全局異常處理器
 * @author admin
 *
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

	
	/**
	 * 處理AccessDeineHandler無權限異常
	 * @param exception
	 * @return
	 */
	@ExceptionHandler(value = AccessDeniedException.class)
	public Result exceptionHandler(AccessDeniedException exception){
		log.error("不允許通路!原因是:",exception.getMessage());
		return Result.forbidden(exception.getMessage());
	}

}
           

三、前端頁面配置

1.  login.html 登入頁面

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    使用者名:<input type="text" name="username"/><br/>
    密碼:<input type="password" name="password"/><br/>
    記住我:<input type="checkbox" name="remember-me" value="true" hidden/><br/>
    <input type="submit" value="登入"/>
</form>
</body>
</html>
           

2.  main.html 首頁面

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登陸成功!!!
<a href="/logout" target="_blank" rel="external nofollow" >退出</a>
<a href="main1.html" target="_blank" rel="external nofollow" >跳轉</a>
</body>
</html>
           

3.  error.html 錯誤頁面

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    操作失敗,請重新登入 <a href= "/login.html">跳轉</a>
</body>
</html>
           

四、驗證效果

上面我們自定義了

userDetailsService

,此時,Spring Security 在其作用流程中會調用自定義登入頁面,會從新通過LoginController調用showLogin方法重定向login.html登入頁面

Spring Security 查詢資料庫(五)

我們輸入使用者名(admin )和密碼(admin)點選登入跳轉 main.html頁面

Spring Security 查詢資料庫(五)

當我在toMain方法中添加 @PreAuthorize("hasRole('ROLE_abc2')") 方法,因為admin方法沒有ROLE_abc2

Spring Security 查詢資料庫(五)

五、RememberMe功能實作

Spring Security 中 Remember Me 為“記住我”功能,使用者隻需要在登入時添加 remember-me複選框,取值為true。Spring Security 會自動把使用者資訊存儲到資料源中,以後就可以不登入進行通路。

編寫配置

/**
 * 記住我配置類
 * @author zhoubin
 * @since 1.0.0
 */
@Configuration
public class RememberMeConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new
                JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自動建表,第一次啟動時需要,第二次啟動時注釋掉
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}
           

修改SecurityConfig.java

http.rememberMe()
                //失效時間,機關秒
                .tokenValiditySeconds(600)
                //登入邏輯交給哪個對象
                .userDetailsService(userDetailsService)
                // 持久層對象
                .tokenRepository(persistentTokenRepository);
           

在用戶端頁面添加複選框

在用戶端登入頁面中添加 remember-me 的複選框,隻要使用者勾選了複選框下次就不需要進行登入了。

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

六、總結

本篇内容,我們通過一個小例子開始介紹了如何給web應用引入Spring Security保護;在展示了http-basic驗證之後,我們使用了記憶體使用者實驗了“角色-資源”通路控制;然後我們介紹了spring security的一些核心概念;之後我們介紹了spring security 是通過filter的形式在web應用中發生作用的,并列舉了filter清單,介紹了入口filter,介紹了springboot是如何載入spring security入口filter的。最後我們通過兩個實戰中的例子展示了spring security的使用。

Spring Security 功能也非常強大,但是還是挺複雜的,本篇内容如有差錯還請指出,謝謝!

項目位址

security-example-03 這個子產品

https://gitee.com/gaibianzlp/spring-security-demo.git

繼續閱讀