日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

菜鸟的spring security学习教程

發(fā)布時間:2023/12/2 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 菜鸟的spring security学习教程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

菜鳥的spring security學習教程

  • 說明
  • 一、Spring Security簡介
  • 二、Spring Security入門系列
    • (1)默認登錄與注銷
    • (2)自定義表單登錄
    • (3)自定義表單用戶授權
    • (4)基于數(shù)據(jù)庫的自定義表單認證
    • (5)基于數(shù)據(jù)庫的自定義表單授權
    • (6)獲取當前登錄用戶的信息
    • (7)前后端分離下的基于表單數(shù)據(jù)的登錄驗證
    • (8)前后端分離下的基于json數(shù)據(jù)的登錄驗證
  • 三、SpringSecurity核心組件
    • (1)Authentication
    • (2)SecurityContext
    • (3)SecurityContextHolder
    • (4)UserDetails
    • (5)UserDetailsService
    • (6)AuthenticationManager
  • 四、部分源碼解析
    • (1)用戶認證流程
      • 認證大致流程
      • 認證具體流程
    • (2)默認登錄用戶名與密碼配置

說明

更新時間:2020/5/31 22:50,更新了基于數(shù)據(jù)庫的認證與授權
更新時間:2020/6/6 17:45,更新了SpringSecurity核心組件

近期要用到spring security這個框架,由于spring security是之前學的,而且當時也沒有深入的學習,對于該框架的用法有點陌生了,現(xiàn)重新學習spring security并在此做好筆記,本文會持續(xù)更新,不斷地擴充

本文僅為記錄學習軌跡,如有侵權,聯(lián)系刪除

一、Spring Security簡介

Spring Security 是 Spring 家族中的一個安全管理框架,主要用于 Spring 項目組中提供安全認證服務,該框架主要的核心功能有認證授權攻擊防護

二、Spring Security入門系列

(1)默認登錄與注銷

文件名:springboot_security2

pom配置

<dependencies><!--thymeleaf--><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.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><!--SpringSecurity框架整合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- thymeleaf和springsecurity5的整合 --><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId><version>3.0.4.RELEASE</version></dependency></dependencies>

經(jīng)過測試,發(fā)現(xiàn)一般只要配置了SpringSecurity之后,即pom導入配置后,只要一訪問控制器的接口,都會被攔截,自動跳轉(zhuǎn)到SpringSecurity自定義的登錄界面,界面如下:

Security自定義的賬號是user,密碼則是由控制臺生成

輸入賬號和密碼即可登錄成功,并跳轉(zhuǎn)到一開始輸入要訪問的頁面

在url后面輸入logout即可退出登錄,logout接口也是Security自己內(nèi)部的接口。

后面真正使用的時候會自己重寫配置,配置自己寫的登錄頁面,以及做一些用戶權限處理。

(2)自定義表單登錄

文件名:springboot_security2
可以看到如果配置了SpringSecurity,Security會有自己的登錄頁面,并且會攔截任何頁面,Security會有自己的內(nèi)部接口login和logout。當然,很多時候是不會用它自己內(nèi)部的登陸頁面,更多的是用自己自定義的登錄頁面,用自定義的表單,只需要自己做一下配置即可。

首先自己新建一個配置類,并且繼承WebSecurityConfigurerAdapter,這是官方要求的,官方說明如下:

/*** Provides a convenient base class for creating a {@link WebSecurityConfigurer}* instance. The implementation allows customization by overriding methods.** <p>* Will automatically apply the result of looking up* {@link AbstractHttpConfigurer} from {@link SpringFactoriesLoader} to allow* developers to extend the defaults.* To do this, you must create a class that extends AbstractHttpConfigurer and then create a file in the classpath at "META-INF/spring.factories" that looks something like:* </p>* <pre>* org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer* </pre>* If you have multiple classes that should be added you can use "," to separate the values. For example:** <pre>* org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer, sample.OtherThatExtendsAbstractHttpConfigurer* </pre>**/意思是說 WebSecurityConfigurerAdapter 提供了一種便利的方式去創(chuàng)建 WebSecurityConfigurer的實例,只需要重寫 WebSecurityConfigurerAdapter 的方法,即可配置攔截什么URL、設置什么權限等安全控制。

創(chuàng)建配置類

package com.zsc.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.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Objects;/*** 沒有添加改配置,頁面會強制跳轉(zhuǎn)到springsecurity自己的登錄頁面* 參考鏈接:https://www.cnblogs.com/dw3306/p/12751373.html*/ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {// 指定密碼的加密方式,不然定義認證規(guī)則那里會報錯@Beanpublic PasswordEncoder passwordEncoder() {return new PasswordEncoder() {@Overridepublic String encode(CharSequence charSequence) {return charSequence.toString();}@Overridepublic boolean matches(CharSequence charSequence, String s) {return Objects.equals(charSequence.toString(), s);}};}//配置忽略掉的 URL 地址,一般用于js,css,圖片等靜態(tài)資源@Overridepublic void configure(WebSecurity web) throws Exception {//web.ignoring() 用來配置忽略掉的 URL 地址,一般用于靜態(tài)文件web.ignoring().antMatchers("/js/**", "/css/**","/fonts/**","/images/**","/lib/**");}// (認證)配置用戶及其對應的角色@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//數(shù)據(jù)在內(nèi)存中定義,一般要去數(shù)據(jù)庫取,jdbc中去拿,/*** 懶羊羊,灰太狼,喜羊羊,小灰灰分別具有vip0,vip1,vip2,vip3的權限* root則同時又vip0到vip3的所有權限*///Spring security 5.0中新增了多種加密方式,也改變了密碼的格式。//要想我們的項目還能夠正常登陸,需要修改一下configure中的代碼。我們要將前端傳過來的密碼進行某種方式加密//spring security 官方推薦的是使用bcrypt加密方式。auth.inMemoryAuthentication().withUser("懶羊羊").password("123").roles("vip0").and().withUser("灰太狼").password("123").roles("vip1").and().withUser("喜羊羊").password("123").roles("vip2").and().withUser("小灰灰").password("123").roles("vip3").and().withUser("root").password("123").roles("vip1","vip2","vip3");}// (授權)配置 URL 訪問權限,對應用戶的權限@Overrideprotected void configure(HttpSecurity http) throws Exception {http.headers().frameOptions().disable();//開啟運行iframe嵌套頁面//任何請求都必須經(jīng)過身份驗證http.authorizeRequests().anyRequest().authenticated();//任何請求都必須經(jīng)過身份驗證//開啟表單驗證http.formLogin().and().formLogin()//開啟表單驗證.loginPage("/toLogin")//跳轉(zhuǎn)到自定義的登錄頁面.usernameParameter("name")//自定義表單的用戶名的name,默認為username.passwordParameter("pwd")//自定義表單的密碼的name,默認為password.loginProcessingUrl("/doLogin")//表單請求的地址,一般與form的action屬性一致,注意:不用自己寫doLogin接口,只要與form的action屬性一致即可.successForwardUrl("/index")//登錄成功后跳轉(zhuǎn)的頁面(重定向).failureForwardUrl("/toLogin")//登錄失敗后跳轉(zhuǎn)的頁面(重定向).and().logout()//開啟注銷功能.logoutSuccessUrl("/toLogin")//注銷后跳轉(zhuǎn)到哪一個頁面.logoutUrl("/logout") // 配置注銷登錄請求URL為"/logout"(默認也就是 /logout).clearAuthentication(true) // 清除身份認證信息.invalidateHttpSession(true) //使Http會話無效.permitAll() // 允許訪問登錄表單、登錄接口.and().csrf().disable(); // 關閉csrf} }

這里配置了幾個用戶懶羊羊,灰太狼,喜羊羊,小灰灰等用于登錄,一般這些用戶要從數(shù)據(jù)庫獲取,另外這里給他們設置了對應的權限,vip0到vip3的權限,都是自己自定義的權限,主要是為了下一節(jié)做授權操作(這里還沒做授權操作)

除此之外,這里有一個自己之前一直搞錯的重點如下

運行并訪問主頁index,會被攔截并且跳到自定義表單

隨便輸入自己上面定義好的用戶,跳轉(zhuǎn)到首頁,并且所有的頁面都可以訪問,vip0到vip3對應所有頁面均可以訪問

注銷登錄(退出)

以上就完成了自定義表單的登錄與注銷,下面開始做用戶授權。

(3)自定義表單用戶授權

文件名:springboot_security2

用戶授權簡單理解就是什么用戶可以訪問什么頁面,不同的用戶可以訪問不同的頁面,上一節(jié)已經(jīng)給不同的用戶設置了的權限,下面給不同用戶做授權,配置類如下

package com.zsc.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.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Objects;/*** 沒有添加改配置,頁面會強制跳轉(zhuǎn)到springsecurity自己的登錄頁面* 參考鏈接:https://www.cnblogs.com/dw3306/p/12751373.html*/ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {// 指定密碼的加密方式,不然定義認證規(guī)則那里會報錯@Beanpublic PasswordEncoder passwordEncoder() {return new PasswordEncoder() {@Overridepublic String encode(CharSequence charSequence) {return charSequence.toString();}@Overridepublic boolean matches(CharSequence charSequence, String s) {return Objects.equals(charSequence.toString(), s);}};}//配置忽略掉的 URL 地址,一般用于js,css,圖片等靜態(tài)資源@Overridepublic void configure(WebSecurity web) throws Exception {//web.ignoring() 用來配置忽略掉的 URL 地址,一般用于靜態(tài)文件web.ignoring().antMatchers("/js/**", "/css/**","/fonts/**","/images/**","/lib/**");}// (認證)配置用戶及其對應的角色@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//數(shù)據(jù)在內(nèi)存中定義,一般要去數(shù)據(jù)庫取,jdbc中去拿,/*** 懶羊羊,灰太狼,喜羊羊,小灰灰分別具有vip0,vip1,vip2,vip3的權限* root則同時又vip0到vip3的所有權限*///Spring security 5.0中新增了多種加密方式,也改變了密碼的格式。//要想我們的項目還能夠正常登陸,需要修改一下configure中的代碼。我們要將前端傳過來的密碼進行某種方式加密//spring security 官方推薦的是使用bcrypt加密方式。auth.inMemoryAuthentication().withUser("懶羊羊").password("123").roles("vip0").and().withUser("灰太狼").password("123").roles("vip1").and().withUser("喜羊羊").password("123").roles("vip2").and().withUser("小灰灰").password("123").roles("vip3").and().withUser("root").password("123").roles("vip1","vip2","vip3");}// (授權)配置 URL 訪問權限,對應用戶的權限@Overrideprotected void configure(HttpSecurity http) throws Exception {http.headers().frameOptions().disable();//開啟運行iframe嵌套頁面//任何請求都必須經(jīng)過身份驗證http.authorizeRequests() // .anyRequest().authenticated()//任何請求都必須經(jīng)過身份驗證.antMatchers("/vip/vip0/**").hasRole("vip0")//vip1具有的權限:只有vip1用戶才可以訪問包含url路徑"/vip/vip0/**".antMatchers("/vip/vip1/**").hasRole("vip1")//vip1具有的權限:只有vip1用戶才可以訪問包含url路徑"/vip/vip1/**".antMatchers("/vip/vip2/**").hasRole("vip2")//vip2具有的權限:只有vip2用戶才可以訪問url路徑"/vip/vip2/**".antMatchers("/vip/vip3/**").hasRole("vip3");//vip3具有的權限:只有vip3用戶才可以訪問url路徑"/vip/vip3/**"//開啟表單驗證http.formLogin().and().formLogin()//開啟表單驗證.loginPage("/toLogin")//跳轉(zhuǎn)到自定義的登錄頁面.usernameParameter("name")//自定義表單的用戶名的name,默認為username.passwordParameter("pwd")//自定義表單的密碼的name,默認為password.loginProcessingUrl("/doLogin")//表單請求的地址,一般與form的action屬性一致,注意:不用自己寫doLogin接口,只要與form的action屬性一致即可.successForwardUrl("/index")//登錄成功后跳轉(zhuǎn)的頁面(重定向).failureForwardUrl("/toLogin")//登錄失敗后跳轉(zhuǎn)的頁面(重定向).and().logout()//開啟注銷功能.logoutSuccessUrl("/toLogin")//注銷后跳轉(zhuǎn)到哪一個頁面.logoutUrl("/logout") // 配置注銷登錄請求URL為"/logout"(默認也就是 /logout).clearAuthentication(true) // 清除身份認證信息.invalidateHttpSession(true) //使Http會話無效.permitAll() // 允許訪問登錄表單、登錄接口.and().csrf().disable(); // 關閉csrf} }

主要增加了用戶權限的配置,具體如下圖

不需要登錄直接進首頁,因為沒對首頁index做限制,但是點擊vip0到vip3的任意頁面都會被攔截,并且自動跳轉(zhuǎn)到登錄頁面進行登錄

點擊vip0下對應的頁面、

灰太狼賬號登錄后,可以訪問vip1權限的頁面,其余權限的頁面不可以訪問,如果訪問會拋出異常,因為沒做相應異常的處理,所以異常會顯示在頁面

登錄具有不同權限的用戶,可以訪問對應權限的頁面,以上就是用戶授權的最基本的用戶。

(4)基于數(shù)據(jù)庫的自定義表單認證

文件名:springboot_security3

首先是數(shù)據(jù)庫的創(chuàng)建,實際上登錄認證至少要有5張表,用戶表角色表權限表角色權限中間表用戶角色中間表,這里按照上面的例子,將權限直接寫死在自定義的SecurityConfig配置類中。

所以這里的登錄認證只涉及到三張表:用戶表(user)、角色表(role)、用戶角色中間表(user_role)。

/*Navicat Premium Data TransferSource Server : test3Source Server Type : MySQLSource Server Version : 80015Source Host : localhost:3306Source Schema : test2Target Server Type : MySQLTarget Server Version : 80015File Encoding : 65001Date: 31/05/2020 22:01:56 */SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;-- ---------------------------- -- Table structure for role -- ---------------------------- DROP TABLE IF EXISTS `role`; CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ---------------------------- -- Records of role -- ---------------------------- INSERT INTO `role` VALUES (1, 'ROLE_vip0'); INSERT INTO `role` VALUES (2, 'ROLE_vip1'); INSERT INTO `role` VALUES (3, 'ROLE_vip2'); INSERT INTO `role` VALUES (4, 'ROLE_vip3');-- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq'); INSERT INTO `user` VALUES (3, '灰太狼', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq'); INSERT INTO `user` VALUES (4, '喜羊羊', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq'); INSERT INTO `user` VALUES (5, '懶羊羊', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq'); INSERT INTO `user` VALUES (6, '小灰灰', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');-- ---------------------------- -- Table structure for user_role -- ---------------------------- DROP TABLE IF EXISTS `user_role`; CREATE TABLE `user_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`uid` int(11) NULL DEFAULT NULL,`rid` int(11) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ---------------------------- -- Records of user_role -- ---------------------------- INSERT INTO `user_role` VALUES (1, 1, 1); INSERT INTO `user_role` VALUES (2, 1, 2); INSERT INTO `user_role` VALUES (3, 1, 3); INSERT INTO `user_role` VALUES (4, 1, 4); INSERT INTO `user_role` VALUES (5, 3, 2); INSERT INTO `user_role` VALUES (6, 4, 3); INSERT INTO `user_role` VALUES (7, 6, 4); INSERT INTO `user_role` VALUES (8, 5, 1);SET FOREIGN_KEY_CHECKS = 1;

具體數(shù)據(jù)表截圖

注意:這里的role跟上面的例子相比多加了ROLE_前綴。這是因為之前的role都是通過springsecurity的api賦值過去的,他會自行幫我們加上這個前綴。但是現(xiàn)在我們使用的是自己的數(shù)據(jù)庫里面讀取出來的權限,然后封裝到自己的實體類中。所以這時候需要我們自己手動添加這個ROLE_前綴。經(jīng)過測試如果不加ROLE_前綴的話,可以做數(shù)據(jù)庫的認證,但無法做授權

創(chuàng)建實體類User,注意User需要實現(xiàn)UserDetails接口,并且實現(xiàn)該接口下的7個接口

package com.zsc.po;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList; import java.util.Collection; import java.util.List;@Data @NoArgsConstructor @AllArgsConstructor public class User implements UserDetails {private Integer id;private String userName;//用戶名private String passWord;//密碼private List<Role> roles;//該用戶對應的角色/*** 返回用戶的權限集合。* @return*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getName()));}return authorities;}/*** 返回賬號的密碼* @return*/@Overridepublic String getPassword() {return passWord;}/*** 返回賬號的用戶名* @return*/@Overridepublic String getUsername() {return userName;}/*** 賬號是否失效,true:賬號有效,false賬號失效。* @return*/@Overridepublic boolean isAccountNonExpired() {return true;}/*** 賬號是否被鎖,true:賬號沒被鎖,可用;false:賬號被鎖,不可用* @return*/@Overridepublic boolean isAccountNonLocked() {return true;}/*** 賬號認證是否過期,true:沒過期,可用;false:過期,不可用* @return*/@Overridepublic boolean isCredentialsNonExpired() {return true;}/*** 賬號是否可用,true:可用,false:不可用* @return*/@Overridepublic boolean isEnabled() {return true;}}

角色表實體類Role,這個類不用實現(xiàn)上述接口

package com.zsc.po;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Role {private Integer id;private String name;//角色的名字 }

接下來做數(shù)據(jù)庫的查詢,創(chuàng)建持久層接口(UserMapper和RoleMapper)

package com.zsc.mapper;import com.zsc.po.User; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; import java.util.List;@Mapper @Repository public interface UserMapper {/*** 通過用戶名獲取用戶信息** @param username 用戶名* @return User 用戶信息*/List<User> getUserByUsername(String username);} package com.zsc.mapper;import com.zsc.po.Role; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; import java.util.List;@Mapper @Repository public interface RoleMapper {/*** 通過用戶id獲取用戶角色集合** @param userId 用戶id* @return List<Role> 角色集合*/List<Role> getRolesByUserId(Integer userId); }

持久層接口對應配置文件(UserMapper.xml和RoleMapper.xml)

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zsc.mapper.UserMapper"><resultMap id = "userMap" type = "com.zsc.po.User"><id column="id" property="id"></id><result column="username" property="userName"></result><result column="password" property="passWord"></result><collection property="roles" ofType="com.zsc.po.Role"><id property="id" column="rid"></id><result column="rname" property="name"></result></collection></resultMap><select id="getUserByUsername" resultMap="userMap">select * from user where username = #{username}</select></mapper> <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zsc.mapper.RoleMapper"><resultMap id = "roleMap" type = "com.zsc.po.Role"><id column="id" property="id"></id><result column="name" property="name"></result></resultMap><select id="getRolesByUserId" resultMap="roleMap">select * from role r,user_role ur where r.id = ur.rid and ur.uid = #{userId}</select></mapper>

創(chuàng)建服務層(UserService),該層獲取數(shù)據(jù)庫數(shù)據(jù),將數(shù)據(jù)交給SpringSecurity做用戶的認證與授權,為此,需要實現(xiàn)接口UserDetailsService,并且實現(xiàn)該接口下的loadUserByUsername方法,該方法獲取數(shù)據(jù)庫數(shù)據(jù)

package com.zsc.service;import com.zsc.mapper.RoleMapper; import com.zsc.mapper.UserMapper; import com.zsc.po.User; import org.springframework.beans.factory.annotation.Autowired; 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 java.util.List;@Service public class UserService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleMapper roleMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {List<User> users = userMapper.getUserByUsername(s);if (null == users || users.size() ==0) {throw new UsernameNotFoundException("該用戶不存在!");}else{users.get(0).setRoles(roleMapper.getRolesByUserId(users.get(0).getId()));System.out.println("***********************"+users.get(0).getAuthorities());return users.get(0);}} }

最后修改一下自定義的SecurityConfig配置類即可

package com.zsc.config;import com.zsc.service.UserService; import org.springframework.beans.factory.annotation.Autowired; 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.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;/*** 沒有添加改配置,頁面會強制跳轉(zhuǎn)到springsecurity自己的登錄頁面* 參考鏈接:https://www.cnblogs.com/dw3306/p/12751373.html*/ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;//配置忽略掉的 URL 地址,一般用于js,css,圖片等靜態(tài)資源@Overridepublic void configure(WebSecurity web) throws Exception {//web.ignoring() 用來配置忽略掉的 URL 地址,一般用于靜態(tài)文件web.ignoring().antMatchers("/js/**", "/css/**","/fonts/**","/images/**","/lib/**");}// (認證)配置用戶及其對應的角色@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());}// (授權)配置 URL 訪問權限,對應用戶的權限@Overrideprotected void configure(HttpSecurity http) throws Exception {http.headers().frameOptions().disable();//開啟運行iframe嵌套頁面//身份驗證http.authorizeRequests().anyRequest().authenticated();//任何請求都必須經(jīng)過身份驗證//開啟表單驗證http.formLogin().and().formLogin()//開啟表單驗證.loginPage("/toLogin")//跳轉(zhuǎn)到自定義的登錄頁面.usernameParameter("name")//自定義表單的用戶名的name,默認為username.passwordParameter("pwd")//自定義表單的密碼的name,默認為password.loginProcessingUrl("/doLogin")//表單請求的地址,一般與form的action屬性一致,注意:不用自己寫doLogin接口,只要與form的action屬性一致即可.successForwardUrl("/index")//登錄成功后跳轉(zhuǎn)的頁面(重定向).failureForwardUrl("/toLogin")//登錄失敗后跳轉(zhuǎn)的頁面(重定向).and().logout()//開啟注銷功能.logoutSuccessUrl("/toLogin")//注銷后跳轉(zhuǎn)到哪一個頁面.logoutUrl("/logout") // 配置注銷登錄請求URL為"/logout"(默認也就是 /logout).clearAuthentication(true) // 清除身份認證信息.invalidateHttpSession(true) //使Http會話無效.permitAll() // 允許訪問登錄表單、登錄接口.and().csrf().disable(); // 關閉csrf} }

大功告成,運行測試,訪問首頁index,被攔截重定向到登錄頁面進行用戶認證,即登錄認證

隨便輸入數(shù)據(jù)庫中存在的任一用戶,密碼是123,數(shù)據(jù)庫存儲的密碼是經(jīng)過加密的,登錄成功,因為沒做任何用戶的授權,所以可訪問任意頁面

這里再重點記錄一下,關于數(shù)據(jù)庫存儲用戶權限必須要有ROLE_前綴,但在SecurityConfig中設置權限時可以不用加ROLE_前綴

這其實是授權部分的內(nèi)容,下一節(jié)就是數(shù)據(jù)庫用戶的授權操作

(5)基于數(shù)據(jù)庫的自定義表單授權

文件名:springboot_security3
關于授權部分,上一節(jié)其實已經(jīng)有講到一點了,實現(xiàn)起來頁簡單,基本的配置跟上一節(jié)一樣保持不變,唯一變的就是將攔截所有請求改為對應權限的攔截,具體只要修改SecurityConfig配置類的部分內(nèi)容即可

package com.zsc.config;import com.zsc.service.UserService; import org.springframework.beans.factory.annotation.Autowired; 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.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;/*** 沒有添加改配置,頁面會強制跳轉(zhuǎn)到springsecurity自己的登錄頁面* 參考鏈接:https://www.cnblogs.com/dw3306/p/12751373.html*/ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;//配置忽略掉的 URL 地址,一般用于js,css,圖片等靜態(tài)資源@Overridepublic void configure(WebSecurity web) throws Exception {//web.ignoring() 用來配置忽略掉的 URL 地址,一般用于靜態(tài)文件web.ignoring().antMatchers("/js/**", "/css/**","/fonts/**","/images/**","/lib/**");}// (認證)配置用戶及其對應的角色@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());}// (授權)配置 URL 訪問權限,對應用戶的權限@Overrideprotected void configure(HttpSecurity http) throws Exception {http.headers().frameOptions().disable();//開啟運行iframe嵌套頁面//身份驗證http.authorizeRequests() // .anyRequest().authenticated();//任何請求都必須經(jīng)過身份驗證.antMatchers("/vip/vip0/**").hasRole("vip0")//vip1具有的權限:只有vip1用戶才可以訪問包含url路徑"/vip/vip0/**".antMatchers("/vip/vip1/**").hasRole("vip1")//vip1具有的權限:只有vip1用戶才可以訪問包含url路徑"/vip/vip1/**".antMatchers("/vip/vip2/**").hasRole("vip2")//vip2具有的權限:只有vip2用戶才可以訪問url路徑"/vip/vip2/**".antMatchers("/vip/vip3/**").hasRole("vip3");//vip3具有的權限:只有vip3用戶才可以訪問url路徑"/vip/vip3/**"//開啟表單驗證http.formLogin().and().formLogin()//開啟表單驗證.loginPage("/toLogin")//跳轉(zhuǎn)到自定義的登錄頁面.usernameParameter("name")//自定義表單的用戶名的name,默認為username.passwordParameter("pwd")//自定義表單的密碼的name,默認為password.loginProcessingUrl("/doLogin")//表單請求的地址,一般與form的action屬性一致,注意:不用自己寫doLogin接口,只要與form的action屬性一致即可.successForwardUrl("/index")//登錄成功后跳轉(zhuǎn)的頁面(重定向).failureForwardUrl("/toLogin")//登錄失敗后跳轉(zhuǎn)的頁面(重定向).and().logout()//開啟注銷功能.logoutSuccessUrl("/toLogin")//注銷后跳轉(zhuǎn)到哪一個頁面.logoutUrl("/logout") // 配置注銷登錄請求URL為"/logout"(默認也就是 /logout).clearAuthentication(true) // 清除身份認證信息.invalidateHttpSession(true) //使Http會話無效.permitAll() // 允許訪問登錄表單、登錄接口.and().csrf().disable(); // 關閉csrf} }

這樣就完成了數(shù)據(jù)庫用戶的授權,測試運行,訪問主頁,跟之前一樣任何人可以登錄,因為沒有對主頁做限制,但是訪問主頁里面的vip0到vip3任意頁面都需要相應的權限,如果沒有會跳到登錄頁面進行登錄認證

(6)獲取當前登錄用戶的信息

登錄授權后,很多時候都需要用戶登錄的用戶的基本信息,比如判斷用戶是在線,獲取當前登錄用戶的關聯(lián)的信息等。都需要用到當前登錄用戶的信息,下面是獲取當前登錄用戶信息的一種方法,主要是在控制層獲取。

@GetMapping("/isLogin")@ResponseBodypublic Object getUserInfo(){if(!SecurityContextHolder.getContext().getAuthentication().getName().equals("anonymousUser")){//已登錄Authentication authentication = SecurityContextHolder.getContext().getAuthentication();//獲取用戶信息//獲取登錄的用戶名String username = authentication.getName();System.out.println("username : "+username);//用戶的所有權限Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();System.out.println("authorities : "+authorities);/*** 如果要獲取更詳細的用戶信息可以采用下面這種方法*///用戶的基本信息User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();System.out.println("user : "+user);//用戶的idInteger userId = user.getId();System.out.println("userId: "+userId);//User其余信息可以用這種方式獲取//List<Role> roles = user.getRoles();//String password = user.getPassword();//String username1 = user.getUsername();return "已登錄賬號:"+username;}else{//未登錄return "請先登錄";}

在沒有登錄的狀態(tài)下訪問上面的接口

登錄灰太狼賬號之后,查看頁面,同時觀察控制臺輸出

以上就是獲取當前登錄賬號的個人信息的全部內(nèi)容。

(7)前后端分離下的基于表單數(shù)據(jù)的登錄驗證

sql

這里新建了一個項目springboot_security4,采用springboot+mybatis-plus+security技術棧,實體類如下

@Data @TableName("user") public class User implements Serializable, UserDetails {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.AUTO)private Integer id;private String username;private String nickname;private String password;private Boolean enabled;private String email;private String userface;@TableField(value = "create_time")//字段名與數(shù)據(jù)庫字段名不一致時采用該形式進行映射private Date createTime;@TableField(value = "update_time")//字段名與數(shù)據(jù)庫字段名不一致時采用該形式進行映射private Date updateTime;/*** 賬號是否失效,true:賬號有效,false賬號失效。* @return*/@Override@JsonIgnore//在json序列化時將pojo中的一些屬性忽略掉,標記在屬性或者方法上,返回的json數(shù)據(jù)即不包含該屬性。public boolean isAccountNonExpired() {return true;}/*** 號是否被鎖,true:賬號沒被鎖,可用;false:賬號被鎖,不可用* @return*/@Override@JsonIgnore//在json序列化時將pojo中的一些屬性忽略掉,標記在屬性或者方法上,返回的json數(shù)據(jù)即不包含該屬性。public boolean isAccountNonLocked() {return true;}/*** 號認證是否過期,true:沒過期,可用;false:過期,不可用* @return*/@Override@JsonIgnore//在json序列化時將pojo中的一些屬性忽略掉,標記在屬性或者方法上,返回的json數(shù)據(jù)即不包含該屬性。public boolean isCredentialsNonExpired() {return true;}/*** 賬號是否可用,true:可用,false:不可用* @return*/@Override@JsonIgnore//在json序列化時將pojo中的一些屬性忽略掉,標記在屬性或者方法上,返回的json數(shù)據(jù)即不包含該屬性。public boolean isEnabled() {return this.enabled;}//如果沒有設置權限的話,這里直接返回null即可@Override@JsonIgnore//在json序列化時將pojo中的一些屬性忽略掉,標記在屬性或者方法上,返回的json數(shù)據(jù)即不包含該屬性。public Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Override@JsonIgnore//在json序列化時將pojo中的一些屬性忽略掉,標記在屬性或者方法上,返回的json數(shù)據(jù)即不包含該屬性。public String getPassword() {return this.password;}@Override@JsonIgnore//在json序列化時將pojo中的一些屬性忽略掉,標記在屬性或者方法上,返回的json數(shù)據(jù)即不包含該屬性。public String getUsername() {return this.username;} }

持久層mapper

@Mapper public interface UserMapper extends BaseMapper<User> { }

user服務層,需要繼承UserDetailsService類,并且實現(xiàn)里面的loadUserByUsername方法
接口

//UserServer接口 public interface UserService extends IService<User> {}

實現(xiàn)類

@Service @Transactional public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {Map<String,Object> map = new HashMap<>();map.put("username",s);List<User> users = userMapper.selectByMap(map);users.forEach(System.out::println);//注意這里必須保證數(shù)據(jù)庫的用戶名唯一if(users.size() == 0 || users.get(0) == null){System.out.println("用戶為null");//避免返回null,這里返回一個不含有任何值的User對象,在后期的密碼比對過程中一樣會驗證失敗return new User();}return users.get(0);}//根據(jù)id查詢用戶public User selectById(Integer id){return userMapper.selectById(id);} }

接下來是配置類WebSecurityConfig ,這個是核心

/*** @ClassName : WebSecurityConfig* @Description : security配置類* @Author : CJH* @Date: 2020-08-31 16:18*/ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserServiceImpl userService;/*** 用戶認證** @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());//使用BCryptPasswordEncoder進行加密}/*** 用戶登錄判斷及響應** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated()//所有的路徑都是登錄后即可訪問.and().formLogin().loginPage("/doLogin")//如果是未登錄的會自動跳到該接口(根據(jù)需要自己實現(xiàn),返回頁面或返回json).successHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();out.write("{\"status\":\"success\",\"msg\":\"登錄成功\"}");out.flush();out.close();}}).failureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=utf-8");PrintWriter out = httpServletResponse.getWriter();out.write("{\"status\":\"error\",\"msg\":\"登錄失敗!!\"}");out.flush();out.close();}}).loginProcessingUrl("/login")//發(fā)起登錄請求的接口.usernameParameter("username")//設置登錄請求接口的參數(shù)(用戶名).passwordParameter("password")//設置登錄請求接口的參數(shù)(密碼).permitAll().and().logout()//注銷登錄接口(/logout).logoutUrl("/logout").logoutSuccessHandler(new LogoutSuccessHandler() {//注銷成功時的處理@Overridepublic void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write("注銷成功");out.flush();out.close();}}).permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());//用戶權限不足時的處理}/*** 用戶權限不足時的處理** @return*/@BeanAccessDeniedHandler getAccessDeniedHandler() {return new AuthenticationAccessDeniedHandler();} }

配置類中對應的用戶權限不足時的處理器

/*** @ClassName : AuthenticationAccessDeniedHandler* @Description : security用戶權限不足時的處理* @Author : CJH* @Date: 2020-08-31 16:59*/ public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {resp.setStatus(HttpServletResponse.SC_FORBIDDEN);resp.setCharacterEncoding("UTF-8");PrintWriter out = resp.getWriter();out.write("權限不足,請聯(lián)系管理員!");out.flush();out.close();} }

控制器LoginController里面的doLogin接口對應上面的WebSecurityConfig里面配置的doLogin

@RestController public class LoginController {/*** 如果自動跳轉(zhuǎn)到這個頁面,說明用戶未登錄,返回相應的提示即可* 如果要支持表單登錄,可以在這個方法中判斷請求的類型,進而決定返回JSON還是HTML頁面* @return*/@RequestMapping("/doLogin")public Map<String,String> doLogin(){Map<String,String> map = new HashMap<>();map.put("msg","尚未登錄,請先登錄");map.put("code","10001");return map;} } /*** <p>* 前端控制器* </p>** @author 最強菜鳥* @since 2020-08-31*/ @RestController @RequestMapping("/user") public class UserController {@GetMapping("/hello")public String hello(){return "hello world";}@GetMapping("/test")public void test(){System.out.println("this is test");} }

上面所做的用戶驗證都是基于表單數(shù)據(jù)的驗證,只有用表單數(shù)據(jù)(非json格式)發(fā)生post請求才有效,下面開始測試


注意:如果用json格式發(fā)請求會驗證失敗,如下

注銷接口

(8)前后端分離下的基于json數(shù)據(jù)的登錄驗證

基本的配置跟上面的(7)一樣,不同的是WebSecurityConfig配置類有部分修改,以及增加了一個過濾器

/*** @ClassName : WebSecurityConfig* @Description : security配置類* @Author : CJH* @Date: 2020-08-31 16:18*/ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserServiceImpl userService;/*** 用戶認證** @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());//使用BCryptPasswordEncoder進行加密}/*** 基于json用戶登錄判斷及響應** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated()//所有的路徑都是登錄后即可訪問.and().formLogin().loginPage("/doLogin")//如果是未登錄的會自動跳到該接口(根據(jù)需要自己實現(xiàn),返回頁面或返回json).loginProcessingUrl("/login")//發(fā)起登錄請求的接口.usernameParameter("username")//設置登錄請求接口的參數(shù)(用戶名).passwordParameter("password")//設置登錄請求接口的參數(shù)(密碼).permitAll().and().logout()//注銷登錄接口(/logout).logoutUrl("/logout").logoutSuccessHandler(new LogoutSuccessHandler() {//注銷成功時的處理@Overridepublic void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write("注銷成功");out.flush();out.close();}}).permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());//用戶權限不足時的處理http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}/*** 自定義security過濾器,以實現(xiàn)用post發(fā)起登錄請求時,參數(shù)用json傳遞* @return* @throws Exception*/@BeanCustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter filter = new CustomAuthenticationFilter();/**登錄成功**/filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write("{\"status\":\"success\",\"msg\":\"登錄成功\"}");out.flush();out.close();}});/**登錄失敗**/filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();out.write("{\"status\":\"error\",\"msg\":\"登錄失敗!!\"}");out.flush();out.close();}});filter.setAuthenticationManager(authenticationManagerBean());return filter;}/*** 用戶權限不足時的處理** @return*/@BeanAccessDeniedHandler getAccessDeniedHandler() {return new AuthenticationAccessDeniedHandler();}}

過濾器

/*** @ClassName : CustomAuthenticationFilter* @Description : 自定義security過濾器,以實現(xiàn)用post發(fā)起登錄請求時,參數(shù)用json傳遞* @Author : CJH* @Date: 2020-08-31 22:18*/ public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//攔截請求頭,可以自定義配置,如果想用表單數(shù)據(jù)也可同時用json也可以用MediaType類型配置,這里只配置了jsonif (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {ObjectMapper mapper = new ObjectMapper();UsernamePasswordAuthenticationToken authRequest = null;try (InputStream is = request.getInputStream()) {Map<String, String> authenticationBean = mapper.readValue(is, Map.class);authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.get("username"),authenticationBean.get("password"));} catch (IOException e) {e.printStackTrace();authRequest = new UsernamePasswordAuthenticationToken("", "");} finally {setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}} else {return super.attemptAuthentication(request, response);}} }

其余的跟上面(7)一樣即可,下面開始測試
訪問未登錄時的接口

訪問登錄接口

注銷接口

注意,使用過濾器進行攔截可以自己根據(jù)需求配置表單數(shù)據(jù)接受或json數(shù)據(jù)接收或兩者都可以接受等

三、SpringSecurity核心組件

這里列舉出以下核心組件:SecurityContextSecurityContextHolderAuthenticationUserdetailsAuthenticationManager,下面開始對這些核心組件的詳細介紹。

(1)Authentication

authentication 直譯過來是“認證”的意思,在Spring Security 中Authentication用來表示當前用戶是誰,一般來講你可以理解為authentication就是一組用戶名密碼信息。Authentication也是一個接口,其定義如下:

我們獲取當前登錄用戶信息就是用的這個接口,如果有看上面入門系列的獲取用戶那一段,就可以知道獲取用戶信息也就是用的Authentication

(2)SecurityContext

安全上下文,用戶通過Spring Security 的校驗之后,驗證信息存儲在SecurityContext中,SecurityContext的接口定義如下:

可以看到這里只定義了兩個方法,主要都是用來獲取或修改認證信息(Authentication)的,Authentication是用來存儲著認證用戶的信息,所以這個接口可以間接獲取到用戶的認證信息。還是以上面的入門系列的獲取用戶那一段來進行解析

(3)SecurityContextHolder

SecurityContextHolder看名字就知道跟SecurityContext實例相關的。在典型的web應用程序中,用戶登錄一次,然后由其會話ID標識。服務器緩存持續(xù)時間會話的主體信息。

但是在Spring Security中,在請求之間存儲SecurityContext的責任落在SecurityContextPersistenceFilter上,默認情況下,該上下文將上下文存儲為HTTP請求之間的HttpSession屬性。它會為每個請求恢復上下文SecurityContextHolder,并且最重要的是,在請求完成時清除SecurityContextHolder

說到SecurityContextHolder就必須要說到一個過濾器,SecurityContextPersistenceFilter

SecurityContextPersistenceFilter:這個Filter是整個攔截過程的入口和出口 ,在請求開始時從配置好的SecurityContextRepository中獲取SecurityContext,然后把它設置給 SecurityContextHolder。在請求完成后將 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同時清除 SecurityContextHolder 所持有的SecurityContext ;

進入源碼查看
同樣的可以參考上面的入門系列的獲取用戶那一段來進行解析

可以看整個登錄用戶的信息獲取流程就十分清晰了。

(4)UserDetails

這個看著有點熟悉,在上面入門系列的基于數(shù)據(jù)庫認證中,實體類User就必須實現(xiàn)這個接口

UserDetails存儲的就是用戶信息,其定義如下:

(5)UserDetailsService

在上面入門系列的基于數(shù)據(jù)庫認證中,用戶類必須要實現(xiàn)UserDetails接口,還需要實現(xiàn)UserDetailsService接口,與實體類User(實現(xiàn)了UserDetails接口)
相對應的還要在應用層中實現(xiàn)UserDetailsService接口

之前在入門系列中的基于數(shù)據(jù)庫的認證中沒有去深究其原理,到現(xiàn)在基本就可以知道其認證的流程,包括數(shù)據(jù)庫用戶的獲取。

通常在spring security應用中,我們會自定義一個UserDetailsService來實現(xiàn)UserDetailsService接口,并實現(xiàn)其loadUserByUsername(final String login);方法。我們在實現(xiàn)loadUserByUsername方法的時候,就可以通過查詢數(shù)據(jù)庫(或者是緩存、或者是其他的存儲形式)來獲取用戶信息,然后組裝成一個UserDetails,(通常是一個org.springframework.security.core.userdetails.User,它繼承自UserDetails) 并返回。

在實現(xiàn)loadUserByUsername方法的時候,如果我們通過查庫沒有查到相關記錄,需要拋出一個異常來告訴spring security來“善后”。這個異常是org.springframework.security.core.userdetails.UsernameNotFoundException。

關于其源碼估計能猜到,肯定有一個loadUserByUsername方法等著我們?nèi)崿F(xiàn)

通常基于數(shù)據(jù)庫的認證,就要從數(shù)據(jù)庫中獲取要認證的用戶信息,從數(shù)據(jù)庫中獲取用戶信息就是通過服務處實現(xiàn)UserDetailsService接口,并重寫其loadUserByUsername方法,這個方法用來獲取數(shù)據(jù)庫用戶信息。

(6)AuthenticationManager

AuthenticationManager 是一個接口,它只有一個方法,接收參數(shù)為Authentication,其定義如下:

public interface AuthenticationManager {Authentication authenticate(Authentication authentication)throws AuthenticationException; }

AuthenticationManager 的作用就是校驗Authentication,如果驗證失敗會拋出AuthenticationException異常。AuthenticationException是一個抽象類,因此代碼邏輯并不能實例化一個AuthenticationException異常并拋出,實際上拋出的異常通常是其實現(xiàn)類,如DisabledException,LockedException,BadCredentialsException等。BadCredentialsException可能會比較常見,即密碼錯誤的時候。

四、部分源碼解析

(1)用戶認證流程

認證大致流程

關于SpringSecurity的用戶認證流程,個人覺得是十分有必要了解的,盡管框架已經(jīng)封裝好,只要按照它定好的規(guī)則來做就好了。在查詢了大量的博客和網(wǎng)上的大量視頻講解后,發(fā)現(xiàn)其實講的基本都一樣,當然有些自己還沒搞懂,個人覺得任何東西如果自己不動手試一下是不可能真正懂的。

首先是大致流程,之前我想的是它可能是通過過濾器的方式去實現(xiàn)的攔截并且重定向到登錄頁面的方式進行認證的,在查閱了大量的資料發(fā)現(xiàn),實現(xiàn)的方式確實是過濾器的方式,只不過它有很多個過濾器,形成一條過濾鏈,只有通過這條過濾鏈后才可以訪問API

具體的驗證流程可以用下圖來表示

下面介紹過濾器鏈中主要的幾個過濾器及其作用:
SecurityContextPersistenceFilter:這個Filter是整個攔截過程的入口和出口 ,在請求開始時從配置好的SecurityContextRepository中獲取SecurityContext,然后把它設置給 SecurityContextHolder。在請求完成后將 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同時清除 SecurityContextHolder 所持有的SecurityContext ;

UsernamePasswordAuthenticationFilter:用于處理來自表單提交的認證。該表單必須提供對應的用戶名和密碼,其內(nèi)部還有登錄成功或失敗后進行處理的AuthenticationSuccessHandler和 AuthenticationFailureHandler,這兩個接口可以字配置,在上面入門系列的自定義的SecurityConfig配置類中可以自己配置

FilterSecuritylnterceptor:是用于保護web資源的,使用AccessDecisionManager對當前用戶進行授權訪問

ExceptionTranslationFilter:捕獲來自FilterChain所有的異常并進行處理。但是它只會處理兩類異 常:Authentication Exception 和 AccessDeniedException ,其它的異常它會繼續(xù)拋出。

認證具體流程

這里推薦一篇個人覺得簡單易懂認證流程的博客:https://www.cnblogs.com/ymstars/p/10626786.html
具體的認證流程需要看源碼才能知道,這里引用一下之前看的視頻的一張認證的圖片,圖片如下

從這里看到請求進來會經(jīng)過UsernamePasswordAuthenticationFilter 過濾器,所以先用全局搜索(CTRL+N)找到該過濾器,并且打上斷點,這里用的默認登錄頁面,密碼用的控制臺隨機生成的密碼

就像上面認證大致流程里面說的一樣,用戶身份的認證交給AuthenticationManager處理,AuthenticationManager又委托給DaoAuthenticationProvider 認證,所以在全局搜索找到DaoAuthenticationProvider并且打上斷點進行調(diào)試

注意這行代碼:
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
它是通過UserDetailsService來加載要驗證的用戶

獲取用戶名后,將用戶名傳給preAuthenticationChecks.check()方法驗證

進入preAuthenticationChecks.check(user);內(nèi)部方法,可以看到有一些驗證,如賬號是否可用,是否被鎖等等,這些參數(shù)就是上面數(shù)據(jù)庫用戶認證User類要認證的參數(shù)

用戶賬號密碼的驗證則是由斷點下面的additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);負責驗證,進入該方法

總結(jié):
(1)UsernamePasswordAuthenticationFilter獲取表單輸入的用戶名和密碼等請求信息,并封裝成Token
(2)AuthenticationManager負責將Token委托給DaoAuthenticationProvider進行認證
(3)DaoAuthenticationProvider通過UserDetailsService來加載要驗證的用戶
(4)最后先校驗用戶的賬號是否被鎖了等信息,再校驗用戶賬號和密碼
(5)校驗成功則可以訪問接口,失敗則拋出異常

以上就是用戶認證具體流程的全部內(nèi)容,涉及到一些源碼解讀,有點累人,剛開始以為很復雜,但自己試著調(diào)試了一下,基本還是可以理解的。

(2)默認登錄用戶名與密碼配置

如果成功引入Security依賴,MVC Security安全管理功能就行會自動生效,默認的安全配置是在UserDetailsServiceAutoConfiguration和SecurityAutoConfiguration中實現(xiàn)的,其中SecurityAutoConfiguration會導入并且自動配置,SpringBootWebSecurityConfiguration用于啟動Web安全管理,UserDetailsServiceAutoConfiguration用于配置用戶信息。

關于Security內(nèi)部配置的用戶名和密碼可以進入源碼查看它的配置,讀源碼真的是一種難受的事情。

總結(jié)

以上是生活随笔為你收集整理的菜鸟的spring security学习教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

99久久99久久 | 久草视频国产 | 久久精品网站视频 | 欧美国产高清 | 久久精品观看 | 国产精品va最新国产精品视频 | 成人免费亚洲 | 色婷婷精品 | 亚洲高清在线观看视频 | 九七视频在线观看 | 欧美片一区二区三区 | 久草在线综合网 | 久久电影网站中文字幕 | 日韩免费播放 | 在线观看免费91 | 日韩中文字幕第一页 | 国产精品第二十页 | 久久激情五月婷婷 | 狠狠狠狠狠狠狠干 | 亚洲高清av | 久久久久久蜜av免费网站 | 欧美日韩一区二区久久 | 在线观看一 | 天天综合天天做 | 在线免费视频a | 国产亚洲片 | 激情一区二区三区欧美 | 国产精品日韩久久久久 | 欧美美女一级片 | 国产精品自产拍 | free,性欧美 九九交易行官网 | 久久精品国产免费看久久精品 | 97精品超碰一区二区三区 | 一区二区激情视频 | 99久久一区 | 免费日韩 精品中文字幕视频在线 | 91麻豆精品国产91久久久更新时间 | 国产999精品久久久久久绿帽 | 欧美亚洲一级片 | 99视屏 | 久久精品视频在线观看 | 国产va精品免费观看 | 久久社区视频 | 久久免费播放 | 欧美有色 | 在线激情影院一区 | 九九九九九九精品任你躁 | 国产精品免费视频久久久 | 国模精品在线 | 色婷婷综合久久久久中文字幕1 | 久久国产手机看片 | 国产色拍拍拍拍在线精品 | 久久久免费观看完整版 | 日韩精品一区二区三区免费观看 | 又色又爽又黄高潮的免费视频 | av免费看电影 | 不卡精品| 国产精品 美女 | 性日韩欧美在线视频 | 中文字幕精品www乱入免费视频 | 久久国产精品99久久久久 | 国产 日韩 欧美 中文 在线播放 | 视频国产区| 爱情影院aqdy鲁丝片二区 | 亚洲精品成人 | 国产日韩在线播放 | 69av网| 成人午夜免费剧场 | 成人精品一区二区三区电影免费 | 成人久久18免费 | 亚洲视频在线视频 | 久久久久久久久久久久电影 | 丝袜美女在线 | 亚洲成av人片一区二区梦乃 | 精品一区二区在线免费观看 | 亚洲国产欧美一区二区三区丁香婷 | 四虎小视频 | 欧美a级在线 | 精品国产自在精品国产精野外直播 | 国产精品日韩在线观看 | 久久精选 | 91免费版在线 | www国产亚洲精品久久麻豆 | www.com久久久 | 日韩一区二区在线免费观看 | 欧美午夜精品久久久久久浪潮 | 91久久国产自产拍夜夜嗨 | 久久高清视频免费 | 国产系列 在线观看 | 97国产精品 | 午夜精品久久久久久99热明星 | 91人人爽久久涩噜噜噜 | 久草视频在线资源站 | 亚洲欧美va | 免费看的黄色的网站 | 西西人体4444www高清视频 | 91黄在线看| 一区二区中文字幕在线 | 国产成人av免费在线观看 | 日韩欧美国产免费播放 | 韩国精品视频在线观看 | 日日噜噜噜噜夜夜爽亚洲精品 | 日韩一二区在线观看 | 96精品高清视频在线观看软件特色 | 97超碰人人澡人人 | 久草手机视频 | 99麻豆久久久国产精品免费 | 日韩久久精品一区二区 | 精品一区二区三区四区在线 | 国内视频1区 | 在线观看v片 | 亚洲国产欧美一区二区三区丁香婷 | 欧美一二三区在线播放 | 亚洲精区二区三区四区麻豆 | 久99久在线| a在线v| 日韩高清免费电影 | 五月天综合激情 | 久久久久久毛片 | 69久久久 | 国产日韩三级 | 日韩在线观看视频中文字幕 | 一区三区在线欧 | 玖玖玖在线 | 亚洲日本一区二区在线 | 81国产精品久久久久久久久久 | 视频一区视频二区在线观看 | 在线视频 精品 | 黄色成人小视频 | 国内精品久久久久影院一蜜桃 | 激情视频亚洲 | 精品一区欧美 | 国产精品国产三级国产 | 99在线精品观看 | 成人一区在线观看 | 久久草在线视频国产 | 91在线看片 | 97成人啪啪网 | 一区二区精品 | 国产精品久久久久影视 | 午夜精品视频一区二区三区在线看 | 91久久国产露脸精品国产闺蜜 | 99热日本| 高潮毛片无遮挡高清免费 | 91亚洲精品久久久蜜桃 | 91av久久| 91高清在线看| 中文字幕免费高 | 午夜少妇| 免费观看黄色12片一级视频 | 成人av在线网 | 97超碰影视| 国产精品理论在线观看 | 久久久综合电影 | 91日韩在线 | 日日夜夜精品 | 91尤物国产尤物福利在线播放 | 国产黄色播放 | 97超碰人人在线 | 欧美激情视频一区二区三区免费 | 国语自产偷拍精品视频偷 | 日日夜夜精品视频天天综合网 | 久久久久草 | 日韩精品一区二区三区免费观看 | 久久综合亚洲鲁鲁五月久久 | 香蕉视频在线免费 | 天天色天天操天天爽 | 亚洲精品乱码久久久久久蜜桃动漫 | 在线成人性视频 | 免费亚洲婷婷 | 91桃色在线观看视频 | 在线视频久| 国产字幕在线看 | 69av久久 | avlulu久久精品 | 中文字幕中文 | 日韩三级视频在线观看 | 99精品国产在热久久下载 | 911久久| 免费精品国产va自在自线 | 精品久久久久久久久久国产 | 国产免费作爱视频 | 久久精品79国产精品 | 欧美日韩精品免费观看视频 | 成人av电影免费在线播放 | 99精品视频免费全部在线 | 奇米影视999| 奇米网网址 | 在线 国产 日韩 | 久草免费福利在线观看 | 2019天天干天天色 | 精品一区二区综合 | 亚洲欧美综合 | 四虎成人网| 91香蕉国产在线观看软件 | 中文国产在线观看 | 久香蕉 | 五月婷婷色| 日本资源中文字幕在线 | 激情婷婷综合 | 亚洲永久国产精品 | 9在线观看免费 | 日韩a免费 | 日韩中文在线视频 | 久久久久色 | 欧美日韩国产三级 | 97香蕉久久国产在线观看 | 亚洲国产中文在线观看 | www.久久成人 | 午夜精品一区二区三区免费视频 | 亚州精品一二三区 | 免费一级黄色 | 国产午夜一区 | 国产69精品久久久久9999apgf | 中文国产在线观看 | 国产福利在线 | 亚洲视频 一区 | 香蕉视频国产在线观看 | 日韩视频在线不卡 | 国产精品久久久久久久久久久久久 | 一区二区三区四区久久 | 国产精品久久久久久久久软件 | 国产在线观看h | 欧美另类性 | 在线亚洲成人 | 久久久免费观看视频 | 中文字幕超清在线免费 | 欧美精品久久久久 | 成人动漫一区二区三区 | 国产二区视频在线 | 在线观看蜜桃视频 | 91精品视频观看 | 中文字幕一区二区三区在线视频 | 日日干美女 | 88av视频 | 91九色蝌蚪视频网站 | 日批视频在线观看免费 | 日韩午夜电影网 | 亚洲精品高清一区二区三区四区 | 91在线观看视频网站 | 欧美日韩久久不卡 | 久久这里只有精品视频首页 | 天天做日日做天天爽视频免费 | 99久久99久久精品免费 | 91色视频 | 亚洲国产高清在线观看视频 | 国产经典三级 | 亚洲黄色小说网 | 中文字幕在线观看你懂的 | 91av在线国产| 青草视频免费观看 | 成人午夜剧场在线观看 | 久久乐九色婷婷综合色狠狠182 | 久久av免费观看 | 九月婷婷综合网 | av久久在线 | 成人黄色大片在线观看 | 亚洲综合视频网 | a视频免费在线观看 | 久久久久久久久福利 | 久草在线中文视频 | 久久精品久久精品久久39 | 丰满少妇麻豆av | 亚洲国产黄色 | 精品一区在线 | 91福利区一区二区三区 | 99色免费视频 | av黄色在线观看 | 中国精品少妇 | 天天干,天天射,天天操,天天摸 | 玖玖玖在线观看 | 射久久久 | 激情五月伊人 | 91精品久久久久久久久 | 992tv在线 | a精品视频| 欧美三级高清 | 91精品视频在线免费观看 | 狠狠的干 | 91精品区| 国产手机在线观看 | 亚洲电影影音先锋 | 亚洲第一区在线播放 | 狠狠色噜噜狠狠狠狠2021天天 | 五月天综合在线 | 精品久久久久久国产偷窥 | av在线看片 | 国产乱码精品一区二区三区介绍 | 国产精品 日韩 | 五月天中文字幕mv在线 | 欧美污污网站 | 69av视频在线观看 | 福利片免费看 | av一级片网站 | 天天操天天曰 | 亚洲国产精品久久久久久 | 在线 欧美 日韩 | 在线看一区| 99久久精品一区二区成人 | 欧美精品亚州精品 | 国产视频观看 | 国产色视频网站 | 亚洲成人资源 | 在线看国产日韩 | 久久久91精品国产一区二区三区 | 国产在线精品播放 | 亚洲精品高清一区二区三区四区 | 亚洲午夜久久久久 | 婷婷丁香激情五月 | 四虎成人免费影院 | 久久福利小视频 | 日韩在线观看三区 | 亚洲一区尤物 | 国产剧情一区二区 | 国产一级黄色电影 | 99视频网站 | 国产日产欧美在线观看 | 99久久精品无码一区二区毛片 | 国产精品久久久久久久久久99 | 日韩特级毛片 | 亚洲永久精品在线 | 久久久久亚洲最大xxxx | 天天爽天天做 | 国产一级不卡视频 | 国产精品情侣视频 | 国产精品日韩久久久久 | 色狠狠久久av五月综合 | 国产日韩在线视频 | 国产精品第二十页 | 少妇bbb搡bbbb搡bbbb | 久久免费黄色 | 国产精品综合久久久久久 | 福利av影院 | 在线观看爱爱视频 | 国产成人久久77777精品 | 狠狠做深爱婷婷综合一区 | 日韩精品久久久久久 | 91香蕉视频污在线 | 久久久污 | 九九热精品视频在线播放 | 黄色大片视频网站 | 午夜在线免费视频 | 超级碰视频 | av免费看看 | 日日爱av | 色射色| 天海冀一区二区三区 | 91九色视频在线观看 | 国产第一页精品 | 国内视频一区二区 | 日批网站免费观看 | 国产黄大片在线观看 | 色a4yy| 国产精品区二区三区日本 | 午夜精品视频免费在线观看 | 欧美国产91 | 在线视频一区观看 | 久久久免费国产 | 在线国产不卡 | 国产成人精品一区二区三区在线 | 中文字幕在线观看网站 | 久久国产亚洲视频 | 在线免费观看的av网站 | 色婷婷天天干 | 99在线视频免费观看 | 国产高清不卡在线 | 99久久精品国 | 久久精品综合一区 | 草免费视频 | 亚洲在线成人精品 | 91大神电影| 久久99精品一区二区三区三区 | 久久五月网 | 天海冀一区二区三区 | 欧美日韩视频精品 | 日韩理论片在线 | 中文字幕视频 | 国产精品九九热 | 最新av免费在线观看 | 在线观看日本高清mv视频 | 久久免费精品视频 | 在线视频日韩 | 亚洲理论在线 | 成人久久18免费网站图片 | 国产中文字幕三区 | 美女视频黄网站 | 视频在线观看99 | 91视频a | 日韩高清黄色 | 久久国产精品免费看 | 96国产在线 | 欧美日韩一二三四区 | 午夜精品久久久久久久久久久久久久 | 91麻豆精品国产91久久久久久 | 国内精品在线观看视频 | 亚洲视频 视频在线 | 欧美一级片在线免费观看 | 激情久久网 | 国产无套视频 | 成人av日韩 | 九月婷婷人人澡人人添人人爽 | 国产美女在线精品免费观看 | 亚洲一区二区三区精品在线观看 | 青春草视频 | 免费黄色小网站 | 久久大视频 | 免费在线观看黄网站 | 欧美嫩草影院 | 天堂中文在线视频 | 精品国产乱码久久久久久浪潮 | 亚洲日韩欧美一区二区在线 | 国产精品免费麻豆入口 | 日本中文字幕在线 | 精品久久久久久久久久久久久久久久久久 | 久久看片网 | 欧美日韩不卡在线视频 | 18久久久| 久久公开免费视频 | 久精品视频在线观看 | 亚洲激情综合 | 国产99久久久国产精品成人免费 | 成人avav | 亚洲狠狠操 | 日韩一区二区三区免费视频 | 精品国产伦一区二区三区观看说明 | 最近高清中文字幕在线国语5 | 精品美女在线视频 | 在线观看中文字幕亚洲 | 伊人国产在线播放 | 国产午夜不卡 | 国产精品一区二区三区免费看 | 成人黄性视频 | 久久国产精品影片 | 天天天干天天射天天天操 | 黄色一级免费网站 | 狠狠操操操 | 日日操天天操夜夜操 | 午夜18视频在线观看 | 亚洲黄色小说网址 | 一本色道久久综合亚洲二区三区 | 在线观看免费观看在线91 | 午夜10000 | 五月婷婷天堂 | 97精品超碰一区二区三区 | 涩涩网站在线观看 | 亚洲禁18久人片 | 国产一区电影在线观看 | 视频福利在线 | 国产精品久久久久免费观看 | 精品99在线 | 人人射人人射 | 夜夜摸夜夜爽 | 亚洲国产精品女人久久久 | 国产视频日韩视频欧美视频 | 国产黄色精品在线 | 中文字幕在线观看一区 | 国产一区二区在线免费观看 | 午夜的福利 | 欧美精品一区在线发布 | 精品少妇一区二区三区在线 | 久久理论视频 | 99在线观看免费视频精品观看 | 国产精彩视频一区二区 | 久草久草久草久草 | 久久成人视屏 | 99久久久国产精品免费99 | 中文字幕av免费观看 | 在线a视频免费观看 | 狠狠的操狠狠的干 | 日韩av资源站 | 久久艹综合 | 99国产精品久久久久久久久久 | 丁香激情网 | 91大片网站 | 免费在线观看一区二区三区 | 麻豆免费在线播放 | 久草在线视频看看 | 人人狠狠综合久久亚洲婷 | 天天色官网 | 4438全国亚洲精品在线观看视频 | 日韩av线观看 | 日三级在线 | 久久久久人人 | 成人av中文字幕在线观看 | 国产精品无 | 五月综合色婷婷 | 国产日韩欧美视频在线观看 | 欧美日韩精品在线免费观看 | 欧美日韩视频观看 | 国产精品一二 | 久久视频这里有久久精品视频11 | 精品视频久久 | 黄色小说在线观看视频 | 婷婷香蕉| 99久精品 | 天海翼一区二区三区免费 | 2021国产精品视频 | a视频在线观看免费 | 国产精品久久久久久久久费观看 | 日韩黄色av网站 | 亚洲一区视频在线播放 | 久久久久国产一区二区三区 | 久久社区视频 | 在线观看的黄色 | 特级西西444www大精品视频免费看 | 日本美女xx | 日韩欧美在线国产 | 中文字幕在线播放av | 美女视频a美女大全免费下载蜜臀 | 亚洲三区在线 | 日韩三级视频在线观看 | 国产成人亚洲精品自产在线 | 91在线网址| 中文字幕成人在线观看 | 日韩久久片| 国产精品高潮呻吟久久av无 | 欧美精品一区二区在线播放 | 日韩成人精品在线观看 | 激情在线免费视频 | 9草在线 | 中文字幕中文字幕在线中文字幕三区 | 亚洲视频免费在线 | 国产99免费视频 | 亚洲成人国产精品 | 黄色大全在线观看 | 久久久久99999 | 国内99视频 | 国产黄网在线 | 草久草久| 午夜精品久久久久久久99 | 亚洲精品视频偷拍 | 超碰国产在线 | 96亚洲精品久久 | 日韩av片免费在线观看 | av在线免费观看黄 | 欧美少妇xxx | 亚洲一区视频免费观看 | 色插综合 | 国产精品毛片久久久久久久久久99999999 | 亚洲视频每日更新 | 精品国产乱码久久久久久三级人 | 久久高清视频免费 | 亚洲欧美精品在线 | 国内精品一区二区 | 精品免费观看视频 | 狠狠色丁香久久婷婷综合丁香 | 国内精品中文字幕 | 波多野结衣综合网 | 国产成人精品综合久久久 | 九九免费在线观看视频 | 日本黄色免费在线观看 | 日本成址在线观看 | 91精品一区在线观看 | 最近能播放的中文字幕 | 91在线精品秘密一区二区 | 波多野结衣亚洲一区二区 | 在线免费观看视频a | 天干啦夜天干天干在线线 | 亚洲国内精品在线 | 四虎4hu永久免费 | 色资源网免费观看视频 | 韩日电影在线 | 日韩精品中文字幕av | 国产男男gay做爰 | 在线国产黄色 | 日韩a级黄色 | 91精品综合在线观看 | 国产综合香蕉五月婷在线 | 免费三级在线 | 91免费网站在线观看 | 久久99精品波多结衣一区 | 在线免费观看的av | 久久免费久久 | 一二区av| www视频免费在线观看 | 亚洲砖区区免费 | 97国产超碰 | www久久99| 亚洲一级黄色片 | 亚洲国产三级在线观看 | 人人澡av | 开心婷婷色 | 99精品国产一区二区三区麻豆 | 欧美成人猛片 | 久久久久久久精 | 国产亚洲精品日韩在线tv黄 | 久久国产成人午夜av影院潦草 | 久久久精品二区 | 99精品国产aⅴ | 久久精品中文字幕 | 久久99久久99精品免视看婷婷 | 国产精品久久久久久久久婷婷 | 一级片免费观看视频 | 国产最顶级的黄色片在线免费观看 | 一区二区三区电影在线播 | 中文字幕乱码亚洲精品一区 | 午夜国产福利在线观看 | 国产二区视频在线观看 | 一区二区三区在线观看免费视频 | 久草.com | 亚洲国产精品日韩 | 国产码电影 | 91在线区 | 波多野结依在线观看 | 免费看黄视频 | 三级av小说 | 69国产成人综合久久精品欧美 | 成人国产精品一区 | 欧美日韩91 | 日韩久久精品一区二区三区下载 | 日韩国产精品毛片 | 国产精品久久久一区二区三区网站 | 国色天香在线观看 | 日韩精品你懂的 | 国产高清网站 | 久久久久久久毛片 | 日韩精品亚洲专区在线观看 | 激情综合网五月激情 | 97国产| 亚洲成人av一区二区 | 亚洲三级毛片 | 欧美激情va永久在线播放 | 97免费在线观看视频 | 国产黄色精品在线 | 亚洲人在线视频 | 欧美日韩综合在线 | 六月色婷 | 黄色1级毛片 | 久久综合导航 | 911在线| 人人爽人人爽av | 91精品国产欧美一区二区成人 | 久操视频在线观看 | 超碰97国产在线 | 国产精品久久久久永久免费 | 97超碰人人澡人人爱 | 欧美在线视频a | 美女视频黄免费的 | 国产在线欧美 | 操操操影院 | 国内一级片在线观看 | 91欧美国产 | 97视频人人澡人人爽 | 精品国产人成亚洲区 | 久久成人国产精品一区二区 | 国产一级黄色片免费看 | 欧美日韩国产一区二区三区在线观看 | 欧美精品久 | 日韩理论电影网 | 亚洲精品久久久久999中文字幕 | 香蕉手机在线 | 美女网站视频免费都是黄 | 福利一区二区三区四区 | 91在线免费公开视频 | 麻豆精品视频 | 天天操天天色综合 | 国产精品久久久久久久久久免费 | 日日爽视频 | 国产精品一区二区电影 | 97视频亚洲 | 狠狠干夜夜操天天爽 | 99久久精品日本一区二区免费 | 在线性视频日韩欧美 | 国产精品一区二区三区在线免费观看 | 婷婷丁香花 | 欧美精品一区二区在线观看 | 69久久99精品久久久久婷婷 | 亚洲 欧美变态 另类 综合 | 国产高清在线视频 | 玖玖爱国产在线 | 综合色站导航 | 精品在线视频观看 | 日韩电影一区二区三区在线观看 | 国语对白少妇爽91 | 激情综合亚洲精品 | 18久久久 | 香蕉视频4aa | 国产一区二区在线免费播放 | 欧美最爽乱淫视频播放 | 成人中文字幕av | 亚洲欧美日韩精品久久久 | 波多野结衣资源 | 99热国内精品 | 免费看黄在线看 | 天天操天天添 | 狠狠操欧美 | 在线免费av网站 | 午夜 久久 tv | 黄网站污 | 国产操在线 | 国产一级精品绿帽视频 | 精品极品在线 | 亚洲国产精品va在线看黑人 | 久久精品一级片 | 日韩一区二区免费视频 | 日韩在线视频一区二区三区 | 在线有码中文字幕 | 国产在线观看免费观看 | 伊人五月婷 | 91桃色在线免费观看 | 久久尤物电影视频在线观看 | 成人av高清 | 日韩天天干 | 一区二区三区四区五区在线 | 在线观看黄色av | 中文字幕一区二区在线播放 | 很黄很黄的网站免费的 | 日韩精品在线观看av | 天堂av官网 | 精品免费久久久久久 | 手机看片99 | 最新在线你懂的 | 玖玖爱免费视频 | 亚洲一级特黄 | 99精品国产在热久久下载 | 中文字幕高清免费日韩视频在线 | 狠狠五月天 | 一区二区三区免费在线播放 | 亚洲电影黄色 | 日韩福利在线观看 | 高清日韩一区二区 | 日本少妇久久久 | 成人免费视频网站在线观看 | 亚洲九九精品 | 色婷婷视频在线观看 | 99热精品免费观看 | 成人一级在线观看 | 久久久亚洲电影 | 在线免费亚洲 | 国产污视频在线观看 | 亚洲国产精久久久久久久 | 亚洲精品婷婷 | www.神马久久 | 亚洲美女精品区人人人人 | 久久综合九色综合久99 | 中文字幕中文字幕在线中文字幕三区 | 欧美一区二区在线看 | 四虎国产精 | 国产精品3 | 国产不卡在线观看视频 | 伊人网av | 337p日本大胆噜噜噜噜 | 色天天久久 | av网站在线观看免费 | 亚洲乱码精品 | 国产精品亚洲片夜色在线 | 欧美日韩不卡一区二区三区 | 精品国产一区二区三区不卡 | 在线视频亚洲 | 国产在线国偷精品产拍免费yy | 久久综合狠狠综合 | 国产91精品一区二区麻豆网站 | 日韩精品久久久久久中文字幕8 | 日韩精品一区二区三区中文字幕 | 中文字幕免费观看全部电影 | 日韩在线免费小视频 | 日本精品一区二区在线观看 | 黄色小说在线观看视频 | 精品免费视频. | 九草在线观看 | 日韩欧美视频在线 | 国产精品久久久久久久免费观看 | 国产 在线 高清 精品 | 最新国产在线视频 | 丁香六月欧美 | 久久手机免费观看 | 成人av地址 | 亚洲成人免费在线观看 | 色视频网页 | 91cn国产在线 | 亚洲 成人 一区 | 精品久久一级片 | 丁香色婷婷 | 91麻豆精品国产自产在线 | 日韩国产精品毛片 | 日韩免费看视频 | 中文字幕免费看 | av免费观看高清 | 天天艹日日干 | 免费观看黄色12片一级视频 | 久久久午夜精品福利内容 | 8x成人免费视频 | 天天综合导航 | 成人9ⅰ免费影视网站 | 在线观看中文av | 日韩在线视频线视频免费网站 | 国产一级免费播放 | 黄色大片视频网站 | 一区二区三区精品在线视频 | 99日精品| 国产九九精品视频 | 日日夜夜狠狠操 | 91禁在线观看 | 日日夜精品 | 丁香激情视频 | 激情网婷婷 | 国产亚洲精品久久久网站好莱 | 一区二区三区免费 | 日韩av免费观看网站 | 日韩在线高清免费视频 | 天堂网一区二区三区 | 国产中文自拍 | 精品一区二区三区久久久 | 国产精品视频一二三 | 韩国精品福利一区二区三区 | 久久亚洲福利视频 | 天天操天天射天天爱 | 久久在草 | 日韩在线视频一区二区三区 | 99免费在线观看视频 | 91视频免费观看 | 婷婷精品国产一区二区三区日韩 | 黄av资源 | 亚洲精品在线一区二区三区 | 91理论片午午伦夜理片久久 | 中文字幕不卡在线88 | 日日夜夜草 | a视频在线播放 | 国产91九色蝌蚪 | 国产精品综合久久 | 国产精品一区二区精品视频免费看 | 丁香婷婷在线观看 | 免费在线观看不卡av | a黄在线观看 | 成人在线免费视频观看 | 久久美女高清视频 | 亚洲国产精品免费 | 夜夜干夜夜 | 国产成人精品日本亚洲999 | 中文字幕.av.在线 | 日韩不卡高清 | 91av视频观看 | 日韩中文字幕亚洲一区二区va在线 | 久久人人爽人人片av | 成人免费观看视频大全 | 国产97视频| 国产精品成人国产乱一区 | 中文字幕在线观看视频免费 | 天天干天天干天天干天天干天天干天天干 | 日韩av中文在线观看 | 国产精品一区二区久久精品爱涩 | 亚洲精品高清视频在线观看 | 2019av在线视频 | 中文字幕视频一区 | 亚洲精品9 | 亚洲激色 | 在线观看色网 | 日本性xxxxx 亚洲精品午夜久久久 | 欧美日韩一区二区久久 | 91尤物在线播放 | 亚洲精品18日本一区app | 国产精品伦一区二区三区视频 | 久久污视频 | 久久精品视频在线播放 | 免费在线观看国产黄 | 国产成人在线免费观看 | 在线a人v观看视频 | 成人av地址 | 天天操天天射天天操 | 久久精品久久国产 | 天天操天天舔天天爽 | 国产精品免费在线观看视频 | 9999亚洲| www.狠狠色.com | 亚洲午夜精品福利 | 一区在线观看视频 | 成人在线一区二区三区 | www.国产精品| 国产视频精品久久 | 蜜臀一区二区三区精品免费视频 | 亚洲精区二区三区四区麻豆 | 中文字幕高清有码 | 在线看片中文字幕 | 国产精品成人免费一区久久羞羞 | 欧美一级特黄aaaaaa大片在线观看 | 婷婷丁香在线视频 | 在线中文字幕av观看 | 欧美精品在线观看一区 | 91热| freejavvideo日本免费 | 欧美日韩在线视频免费 | 99久久久国产精品免费99 | 久久视频免费在线观看 | 久草在线综合网 | 欧美国产日韩一区二区三区 | 久久精品爱爱视频 | 中文字幕在线观看你懂的 | 在线观看av网站 | 欧美日韩网址 | 色婷婷激情电影 | 精品国产一区在线观看 | 色av婷婷 | 偷拍精品一区二区三区 | 91毛片在线观看 | 啪啪精品| 最近2019年日本中文免费字幕 | 99亚洲视频| 国产成人一区二区三区电影 | 婷婷丁香六月天 | 成人在线免费视频 | 国产69精品久久99不卡的观看体验 | 国产视频一区二区在线观看 | 国产不卡毛片 | 成人av电影在线 | 婷婷综合电影 | 亚洲97在线 | 中文字幕亚洲精品在线观看 | 欧美日韩国产精品一区二区三区 | 色婷婷视频在线观看 | 成人小电影在线看 | 521色香蕉网站在线观看 | 亚洲国产经典视频 | 黄视频网站大全 | 综合色在线观看 | 在线亚洲天堂网 | 免费情缘 | 成人片在线播放 | 中文字幕视频播放 | 黄av免费 | 在线观看岛国 | 午夜性生活片 | 国产中文欧美日韩在线 | 黄色成人影视 | 日本视频久久久 | 久久久一本精品99久久精品66 | 久久久久久久久久网站 | 国产在线观看中文字幕 | 激情综合五月网 | 久久久国产一区二区三区四区小说 | 99电影456麻豆 | 国产视频精品网 | 99 精品 在线 | 五月天,com| 国产视频一区在线免费观看 | 97香蕉久久超级碰碰高清版 | 激情婷婷色 | 中文成人字幕 | 国产成人综合在线观看 | 夜添久久精品亚洲国产精品 | 日韩高清成人 | 亚洲精品视频久久 | 日韩在线观看网址 | 国产视频一二区 | 国产精品区二区三区日本 | 91最新在线视频 | 天天干天天插伊人网 | 久久精美视频 | 91成人免费观看视频 | 国产精品免费观看国产网曝瓜 | 午夜精品福利一区二区 | 五月的婷婷 | 亚洲高清视频一区二区三区 | 成片免费观看视频大全 | 亚洲三级影院 | 国产日韩在线观看一区 | 又紧又大又爽精品一区二区 | 五月综合激情婷婷 | 成人国产精品入口 | 精品久久片 | 91传媒在线看 | 国产精品a成v人在线播放 | av成人免费在线观看 | 在线观看你懂的网站 | 色偷偷88888欧美精品久久久 | 中文字幕日韩一区二区三区不卡 | 国产视频手机在线 | 欧美日韩免费一区二区三区 | 亚洲一区二区三区在线看 | 婷婷丁香视频 | 国产一区成人在线 | 久久精国产 | 日韩精品中字 | 久草精品视频在线看网站免费 | 免费亚洲黄色 | 久久99亚洲精品久久 | 国产精品国产三级国产 | 天堂av在线网址 | 丁香婷婷色综合亚洲电影 | av最新资源 | 91亚洲精| av免费在线网站 | 国产护士av | av电影中文字幕在线观看 | 日p在线观看 | 免费三级a| 狠狠干夜夜 | 在线国产激情视频 | japanesefreesexvideo高潮 | 国产精品精品 | 在线观看911视频 | 日韩av电影手机在线观看 | 日韩在线首页 | 三级免费黄| 色妞久久福利网 | av不卡免费看 | 久久国产亚洲 | 亚洲精品1区2区3区 超碰成人网 | 国产91精品一区二区绿帽 |