Spring Security進階
1.連線資料庫進行資料的驗證
Spring Security進行身份驗證或者許可權控制時,使用者名稱和密碼應該要和資料庫的進行比較才行,使用者的各種資訊我們從資料庫中去獲取,不用自己在程式碼或者配置檔案中寫。
案例
1)建立專案
自己建立一個Maven專案
2)匯入依賴
<parent>
<!--Spring boot-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<dependencies>
<!--Spring boot Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--資料庫連線框架JPA-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
3)建立啟動類
@SpringBootApplication
public class SecurityApplication2 {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication2.class, args);
}
}
4)配置檔案
在專案的resources檔案的目錄下建立一個配置檔案:application.properties
#連線資料庫
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/你的資料庫?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=使用者名稱
spring.datasource.password=密碼
#資料庫表的生成
spring.jpa.generate-ddl=true
#顯示執行的sql語句
spring.jpa.show-sql=true
#使用的資料庫
spring.jpa.database=mysql
5)建立類
來建立一個實體層類、dao層介面、service層、控制層
實體類
//這個註解表示這個是實體類,對應資料庫中的表,預設實體名就是類名
@Entity
public class UserInfo {
//這個屬性的表中的主鍵
@Id
//主鍵的生成策略,IDENTITY 主鍵自增長
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//使用者名稱
private String username;
//密碼
private String password;
//角色
private String roles;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
}
dao層介面
/*
* 繼承JpaRepository<UserInfo,Long>介面,泛型一個是實體類,一個是主鍵型別
* */
public interface UserInfoDao extends JpaRepository<UserInfo,Long> {
//根據使用者名稱查詢使用者
//(溫馨提示:想要查詢的話方法名要為 findBy屬性欄位 刪除的話 deleteBy屬性欄位 ...以此類推這樣才不報錯)
UserInfo findByUsername(String username);
}
service層
public interface UserInfoService {
//根據使用者名稱查詢使用者
UserInfo findByUsername(String username);
}
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
//根據使用者名稱返回使用者
@Override
public UserInfo findByUsername(String username) {
UserInfo user = userInfoDao.findByUsername(username);
return user;
}
}
controller層
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {
@RequestMapping("common")
//方法執行前驗證使用者是否有該角色
@PreAuthorize(value = "hasAnyRole('normal','admin')")
public String commonUserInfo() {
return "==測試資料庫 有兩個角色==";
}
@RequestMapping("admin")
//方法執行前驗證是否有該角色
@PreAuthorize(value = "hasAnyRole('admin')")
public String adminUserInfo() {
return "==測試資料庫 有一個角色==";
}
}
這些準備工作都做完之後,我們可以往資料庫中插入一些資料,建立一個類,用來往資料庫中新增資料的。
@Component
public class InitJdbc {
@Autowired
private UserInfoDao userInfoDao;
//Java自帶的註解,程式啟動的時候執行該方法,每啟動一次執行一次,
//插入成功了,再啟動專案的時候不想再重複插入,可以把@PostConstruct註釋掉
@PostConstruct
public void init() {
//密碼要加密
PasswordEncoder pe = new BCryptPasswordEncoder();
//新增一個使用者
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangwu");
userInfo.setPassword(pe.encode("123456"));
userInfo.setRoles("normal");
userInfoDao.save(userInfo);
//新增一個使用者
UserInfo userInfo1 = new UserInfo();
userInfo1.setUsername("admin");
userInfo1.setPassword(pe.encode("admin"));
userInfo1.setRoles("admin");
userInfoDao.save(userInfo1);
}
}
現在就可以啟動專案看看資料庫中是否插入成功資料了。
接下來做許可權的驗證==========
來寫一個類,實現UserDetailService並實現它的方法
//把類交給spring容器管理
@Component
public class SecurityDetail implements UserDetailsService {
@Autowired
private UserInfoDao userInfoDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserInfo userInfo = null;
//UserDetails介面的實現類
User user = null;
//判斷使用者名稱不為空
if (s != null) {
//根據使用者名稱查詢使用者
userInfo = userInfoDao.findByUsername(s);
//判斷使用者不為空
if (userInfo != null) {
//User類的第三個引數是集合,所有建立一個集合,獲取使用者的角色
List<GrantedAuthority> list = new ArrayList<>();
//獲取使用者的角色,獲取到的角色開頭一定要以"ROLE_"開頭
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + userInfo.getRoles());
list.add(authority);
//User的構造器要返回三個引數:使用者名稱、密碼、集合的角色
user = new User(userInfo.getUsername(), userInfo.getPassword(), list);
}
}
return user;
}
}
再寫一個配置類,解析密碼的加密方式
@Configuration
@EnableWebSecurity
//開啟方法級別的驗證
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityDetail securityDetail;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//密碼加密的方式
auth.userDetailsService(securityDetail).passwordEncoder(new BCryptPasswordEncoder());
}
}
現在可以進行測試了,啟動專案,訪問控制層的方法,只有方法上標記的角色才能訪問該方法
2.認證和授權
authentication:認證,認證訪問的使用者是不是有效的使用者,他是誰。
authorization:授權,訪問的使用者在系統中能幹什麼
RBAC:基於角色的訪問控制(Role-Based Access Control),使用者屬於某個角色,而角色擁有某些許可權。
許可權:能對資源進行操作,比如增刪改查
角色:自定義的,表示許可權的聚合,一個角色可以有多個許可權。
舉例說明:
設計角色:經理具有資料的修改、刪除、檢視等;員工只能檢視資料。
一個公司中如果想把一個使用者設定為經理,只需把他設定為經理這個角色,他就能有修改、刪除、檢視等操作了,如果公司新來普通員工,只需把他加入到員工這個角色裡面就好了。這樣,想讓什麼使用者使用者什麼許可權,只需把他加入到相應的角色裡就OK了。
UserDetailService:這是一個介面,裡面只有一個方法UserDetails loadUserByUsername(String var1),是根據使用者名稱來獲取資料庫中資訊的
主要的實現有:
InMemoryUserDetailsManager在記憶體中維護使用者資訊的,使用很方便,可是資料不是持久的
JdbcUserDetailsManager對資料庫資訊進行操作的,底層是基於jdbcTemplate的,可以使用這個類的方法來運算元據庫資料。
UserDetails:提供使用者資訊的核心介面
// 許可權的集合 Collection<? extends GrantedAuthority> getAuthorities(); //獲取密碼 String getPassword(); //獲取使用者名稱 String getUsername(); //使用者是否存在 boolean isAccountNonExpired(); //使用者是否鎖定 boolean isAccountNonLocked(); //證照是否過期 boolean isCredentialsNonExpired(); //賬戶是否啟用 boolean isEnabled();
UserDetails有一個實現類User
//他有兩個構造器,引數和UserDetails的欄位屬性一樣 public User(String username, String password, Collection<? extends GrantedAuthority> authorities) { this(username, password, true, true, true, true, authorities); } public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { if (username != null && !"".equals(username) && password != null) { this.username = username; this.password = password; this.enabled = enabled; this.accountNonExpired = accountNonExpired; this.credentialsNonExpired = credentialsNonExpired; this.accountNonLocked = accountNonLocked; this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); } else { throw new IllegalArgumentException("Cannot pass null or empty values to constructor"); } }
2.1設定表
基於RBAC設定三張表,使用者表,角色表,使用者和角色的關聯表 密碼明文分別是 123 456 admin
-- 角色表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL,
`rolename` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名稱',
`rolememo` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'USER', '普通使用者');
INSERT INTO `sys_role` VALUES (2, 'READ', '只讀');
INSERT INTO `sys_role` VALUES (3, 'ADMIN', '管理員');
SET FOREIGN_KEY_CHECKS = 1;
-- 使用者表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`realname` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '真實名字',
`isenable` int(11) NULL DEFAULT NULL COMMENT '是否開啟認證',
`islock` int(11) NULL DEFAULT NULL COMMENT '是否鎖定',
`isexpire` int(11) NULL DEFAULT NULL,
`incredentials` int(255) NULL DEFAULT NULL COMMENT '是否過期',
`createtime` date NULL DEFAULT NULL,
`logintime` date NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user 密碼明文分別是 123 456 admin
-- ----------------------------
INSERT INTO `sys_user` VALUES (6, 'zs', '$2a$10$EGMo2XSdh49cDgXa0OzXYu36HfNssUf7zUDaNIz83AgWveA3GORYq', '張三', 1, 1, 1, 1, '2021-09-02', '2021-09-02');
INSERT INTO `sys_user` VALUES (7, 'lisi', '$2a$10$r9iLBYZzIIt/gyOngvPnZOBZaP4EW58etU1tLPoEh7hlYpydIaM6u', '李四', 1, 1, 1, 1, '2021-09-02', '2021-09-02');
INSERT INTO `sys_user` VALUES (8, 'admin', '$2a$10$P.I3zf7bEAmLmlSwaDOdMOdrxEyTT1QvbqfKC5YGQ7zHk5zUR/dCG', '管理員', 1, 1, 1, 1, '2021-09-02', '2021-09-02');
SET FOREIGN_KEY_CHECKS = 1;
--關聯表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`userid` int(11) NOT NULL,
`roleid` int(11) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (6, 1);
INSERT INTO `sys_user_role` VALUES (7, 2);
INSERT INTO `sys_user_role` VALUES (8, 1);
INSERT INTO `sys_user_role` VALUES (8, 3);
SET FOREIGN_KEY_CHECKS = 1;
2.2建立專案
2.3 匯入依賴
把上一個專案的依賴匯入進來,再加多一個spring整合mybatis的包
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
2.4建立相關類
2.4.1實體類
建立User和Role的實體類,要繼承UserDetails
public class SysUser implements UserDetails {
private Integer id;
private String username;
private String password;
private String realName;
private boolean isEnable;
private boolean isExpired;
private boolean isLock;
private boolean isCredentials;
private List<GrantedAuthority> grantedAuthorities;
private Date createTime;
private Date loginTime;
public SysUser() {
}
public SysUser(String username, String password, String realName,
boolean isEnable, boolean isExpired, boolean isLock,
boolean isCredentials, List<GrantedAuthority> grantedAuthorities,
Date createTime, Date loginTime) {
this.username = username;
this.password = password;
this.realName = realName;
this.isEnable = isEnable;
this.isExpired = isExpired;
this.isLock = isLock;
this.isCredentials = isCredentials;
this.grantedAuthorities = grantedAuthorities;
this.createTime = createTime;
this.loginTime = loginTime;
}
//角色的集合
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
//密碼
@Override
public String getPassword() {
return password;
}
//使用者名稱
@Override
public String getUsername() {
return username;
}
//賬號是否存在
@Override
public boolean isAccountNonExpired() {
return isExpired;
}
//賬號是否鎖定
@Override
public boolean isAccountNonLocked() {
return isLock;
}
//是否過期
@Override
public boolean isCredentialsNonExpired() {
return isCredentials;
}
//是否啟用
@Override
public boolean isEnabled() {
return isEnable;
}
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setRealName(String realName) {
this.realName = realName;
}
public void setEnable(boolean enable) {
isEnable = enable;
}
public void setExpired(boolean expired) {
isExpired = expired;
}
public void setLock(boolean lock) {
isLock = lock;
}
public void setCredentials(boolean credentials) {
isCredentials = credentials;
}
public void setGrantedAuthorities(List<GrantedAuthority> grantedAuthorities) {
this.grantedAuthorities = grantedAuthorities;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public void setLoginTime(Date loginTime) {
this.loginTime = loginTime;
}
public Integer getId() {
return id;
}
public String getRealName() {
return realName;
}
public Date getCreateTime() {
return createTime;
}
public Date getLoginTime() {
return loginTime;
}
@Override
public String toString() {
return "SysUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", realName='" + realName + '\'' +
", isEnable=" + isEnable +
", isExpired=" + isExpired +
", isLock=" + isLock +
", isCredentials=" + isCredentials +
", grantedAuthorities=" + grantedAuthorities +
", createTime=" + createTime +
", loginTime=" + loginTime +
'}';
}
}
public class SysRole {
private Integer id;
private String role;
private String rolememo;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getRolememo() {
return rolememo;
}
public void setRolememo(String rolememo) {
this.rolememo = rolememo;
}
@Override
public String toString() {
return "SysRole{" +
"id=" + id +
", role='" + role + '\'' +
", rolememo='" + rolememo + '\'' +
'}';
}
}
2.4.2dao層和對應的xml檔案
@Repository
public interface SysRoleMapper {
//根據使用者id檢視角色
List<SysRole> selectByUserId(Integer userId);
}
@Repository
public interface SysUserMapper {
//插入使用者
int insertSysUser(SysUser sysUser);
//根據使用者名稱查詢使用者
SysUser selectByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.huang.security.mapper.SysRoleMapper">
<resultMap id="roleMapper" type="com.huang.security.entity.SysRole">
<id column="id" property="id"/>
<result column="rolename" property="role"/>
<result column="rolememo" property="rolememo"/>
</resultMap>
<select id="selectByUserId" resultMap="roleMapper" >
SELECT sr.id,sr.rolename,sr.rolememo FROM sys_role AS sr
INNER JOIN sys_user_role AS sur ON sr.id = sur.roleid
WHERE sur.userid = #{userid}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.huang.security.mapper.SysUserMapper">
<resultMap id="userMapper" type="com.huang.security.entity.SysUser">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="realname" property="realName"/>
<result column="isenable" property="isEnable"/>
<result column="islock" property="isLock"/>
<result column="isexpire" property="isExpired"/>
<result column="incredentials" property="isCredentials"/>
<result column="createtime" property="createTime"/>
<result column="logintime" property="loginTime"/>
</resultMap>
<insert id="insertSysUser" parameterType="com.huang.security.entity.SysUser">
insert into sys_user(username,password,realname,isenable,islock,incredentials,createtime,logintime)
values(#{username},#{password},#{realName},#{isEnable},#{isLock},#{isCredentials},
#{createTime},#{loginTime})
</insert>
<select id="selectByUsername" resultMap="userMapper" >
select id,username,password,realname,isenable,islock,isexpire,incredentials,createtime,logintime
from sys_user where username = #{username}
</select>
</mapper>
2.5service層
service層要實現UserDetailService介面,去獲取資料庫中的資訊做返回
@Service
public class UserWnoRoleService implements UserDetailsService {
@Autowired
private SysUserMapper userMapper;
@Autowired
private SysRoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根據使用者名稱獲取使用者
SysUser user = userMapper.selectByUsername(username);
System.out.println("==== Service =====");
String roleName = "";
List<GrantedAuthority> list = new ArrayList<>();
System.out.println("User" + user);
if (!StringUtils.isEmpty(user)) {
//根據使用者id獲取對應角色
List<SysRole> roles = roleMapper.selectByUserId(user.getId());
for (SysRole role : roles) {
//一個使用者可能有多個角色,用集合儲存,放到使用者的集合裡
roleName = role.getRole();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + roleName);
list.add(authority);
user.setGrantedAuthorities(list);
}
//返回的這個user是包含角色的
return user;
}
//可以返回自定義user,是因為實體類實現了UserDetails這個介面
return user;
}
}
2.6配置檔案
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/庫名?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=使用者名稱
spring.datasource.password=密碼
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
# 包起別名
mybatis.type-aliases-package=com.huang.security.entity
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2.7相關的配置類
@Configuration
//@EnableWebSecurity //如果是匯入的jar包是spring-boot-starter-security可以不用寫
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("====== MySecurityConfig configure==============");
//匹配"/index","/login.html","/login" 不用驗證(permit 許可),和登入相關的要放行
http.authorizeRequests().antMatchers("/index","/login.html","/login").permitAll()
//匹配只有相關角色才能訪問的路徑
.antMatchers("/access/user/**").hasRole("USER")
.antMatchers("/access/read/**").hasRole("READ")
.antMatchers("/access/admin/**").hasRole("ADMIN")
//所有都需要驗證
.anyRequest().authenticated()
//執行結束
.and()
//表單的方式登入
.formLogin()
//登入的自定義檢視頁面
.loginPage("/login.html")
//登入訪問的地址,表單中action的值
.loginProcessingUrl("/login")
.and()
//跨域安全的設定,禁用
.csrf().disable();
}
@Qualifier("userWnoRoleService")
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
// userDetailsService使用的是service層的 UserWnoRoleService,它實現了 UserDetailsService
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
2.8html頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
身份驗證 <br>
<a href="/access/user">zs</a> <br>
<a href="/access/read">lisi</a> <br>
<a href="/access/admin">admin</a> <br>
<a href="/logout">退出</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>自定義登入頁</p>
<form action="/login" method="post">
使用者名稱:<input type="text" name="username" value=""><br/>
密 碼:<input type="password" name="password" value=""><br/>
<input type="submit" value="登入">
</form>
</body>
</html>
2.9controller層測試
@Controller
public class InitController {
@GetMapping("index")
public String toIndex() {
return "forward:/index.html";
}
}
@RestController
@RequestMapping("/access")
public class UserWnoRoleController {
@GetMapping("user")
public String sayUser() {
return "zs 是 user 角色";
}
@GetMapping("read")
public String sayRead() {
return "lisi 是 read 角色";
}
@GetMapping("admin")
public String sayAdmin() {
return "admin 是 user admin 角色";
}
}
個人筆記