最近在做一些練習的demo,所以好久沒有更新博客了。這一篇來記錄一下在SpringBoot
中整合SpringSecurity
技術,並實現通過數據庫的方式來認證用戶。
提前聲明一下,這一篇沒有授權相關代碼配置,只有認證哦.
文章目錄
maven依賴準備
這裏直接上pom.xml
文件:
<?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.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot_springsecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_springsecurity</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-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
數據庫準備(建表語句省略)
數據庫設計,權限認證無非就是用戶和角色,一個用戶可以擁有多個角色,一個角色可以屬於多個用戶。所以它們是典型的多對多關係。數據庫表可以這樣子
-
有一張用戶表
users
(user_id,username,password,enabled,locked):
-
角色表
roles
():
-
它們的關聯表
user_role
(id,ur_user_id,ur_role_id),後面兩個字段記得設置相關聯的外鍵:
對應的實體類對象準備:
- 角色對象(
Role
):
public class Role {
private Integer role_id;
private String name;
private String nameZH;
public String getNameZH() {
return nameZH;
}
public void setNameZH(String nameZH) {
this.nameZH = nameZH;
}
@Override
public String toString() {
return "Role{" +
"role_id=" + role_id +
", name='" + name + '\'' +
", nameZH='" + nameZH + '\'' +
'}';
}
public Integer getRole_id() {
return role_id;
}
public void setRole_id(Integer role_id) {
this.role_id = role_id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 用戶對象(
User
)裏面除了要有數據庫中的字段,還需要有一個List集合來存放該用戶的角色信息,同時還需要實現UserDetails
接口,這個接口是SpringSecurity內部用來存放認證對象的規範:
public class User implements UserDetails {
private Integer user_id;
private String username;
private String password;
private List<Role> roleList;
private Boolean enabled;
private Boolean locked;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
//SimpleGrantedAuthority其實是封裝角色信息
for (Role role : roleList) {
authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
//是否未過期,爲了方便直接true
@Override
public boolean isAccountNonExpired() {
return true;
}
//是否沒有被鎖定
@Override
public boolean isAccountNonLocked() {
return !locked;
}
//密碼是否未過期,同理
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//是否可用
@Override
public boolean isEnabled() {
return enabled;
}
public Integer getUser_id() {
return user_id;
}
public void setUser_id(Integer user_id) {
this.user_id = user_id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public Boolean getLocked() {
return locked;
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
@Override
public String toString() {
return "User{" +
"user_id=" + user_id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", roleList=" + roleList +
", enabled=" + enabled +
", locked=" + locked +
'}';
}
}
一些方法說明寫在註釋裏了,同時剛剛數據庫表中,有兩個字段(enabled
,locked
)作用出來了,因爲UserDetails
接口有這兩個屬性,而Security底層會調用這些屬性來判斷該用戶是否可用。爲了方便,我們統統設置成可用。
mapper層代碼
接下來還是按照以前的三層架構進行開發,首先mapper
層代碼,這裏選用的持久化技術爲myBatis,因爲涉及到多對多關係的封裝,所以我們採用基於xml的方式.
UserMapper.java
(添加方法後面會用到,所以先寫上):
@Mapper
public interface UserMapper {
//根據username查找
public User loadUserByUsername(String username);
//添加用戶
public Integer addUser(User user);
}
RoleMapper.java
:
@Mapper
public interface RoleMapper {
//根據id查找角色
public Role getRoleByRole_id(Integer role_id);
}
UserMapper.xml
:
<?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.example.springboot_springsecurity.mapper.UserMapper">
<resultMap id="userMapper" type="com.example.springboot_springsecurity.entity.User">
<id property="user_id" column="user_id"></id>
<result property="username" column="username"></result>
<result property="password" column="password"></result>
<result property="enabled" column="enabled"></result>
<result property="locked" column="locked"></result>
<collection property="roleList" javaType="ArrayList" ofType="com.example.springboot_springsecurity.entity.Role" select="com.example.springboot_springsecurity.mapper.RoleMapper.getRoleByRole_id" column="role_id"></collection>
</resultMap>
<select id="loadUserByUsername" parameterType="String" resultMap="userMapper">
select * from users inner join user_role on user_id = ur_user_id inner join roles on role_id = ur_role_id where username = #{username}
</select>
<insert id="addUser" parameterType="com.example.springboot_springsecurity.entity.User">
insert into users(username,password) values (#{username},#{password})
</insert>
</mapper>
RoleMapper.xml
:
<?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.example.springboot_springsecurity.mapper.RoleMapper">
<select id="getRoleByRole_id" parameterType="Integer" resultType="com.example.springboot_springsecurity.entity.Role">
select * from roles where role_id = #{role_id};
</select>
</mapper>
其中UserMapper.xml
中有一個collection
的使用,用來封裝集合的,這個要注意。
Service
到了Service層代碼,這裏只用到一個UserService
類,在此之前需要實現UserDetailsService
接口,原理大傢伙應該在很多博客上看過了,簡單來說就是基於數據庫認證時,底層會調用這個接口的方法進行認證判斷,我們只需要實現這個接口的方法,裏面編寫相應的代碼即可.擴展性槓槓滴:
UserService:
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(s);
if (user == null)
throw new UsernameNotFoundException("用戶名密碼錯誤!");
return user;
}
}
可以看到,loadUserByUsername
這個方法是我們需要實現的,裏面邏輯也很簡單,調用我們編寫的mapper類,進行查找,如果沒找到就報錯,找到了就返回給Security,剩下的就不用我們操心了。
Controller
Controller
層代碼就簡單了,就返回一個字符串即可:
@RestController
public class UserController {
@PostMapping("/demo")
public String demo(){
return "hello,world!";
}
}
Security的配置類(不使用加密方式):
先介紹數據庫密碼不使用加密的方式,編寫一個SpringSecurityConfig
配置類,繼承WebSecurityConfigurerAdapter
它。
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用數據庫認證,參數爲一個UserService類型的對象
auth.userDetailsService(userService);
super.configure(auth);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.rememberMe();
//登錄成功後訪問這個路徑,就是剛剛controller的路徑
http.formLogin().successForwardUrl("/demo");
super.configure(http);
}
@Bean
NoOpPasswordEncoder noOpPasswordEncoder(){
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}
其中NoOpPasswordEncoder
代表就是不使用加密密碼.
接下來手動在數據庫中插入一個user記錄:
同時也插入一些角色記錄:
最後在關聯表插入它們的關聯記錄:
寫到這裏才發現,其實只記錄認證的話,角色是用不到的…但是寫也寫了,就當溫故知新吧!
接着開始測試,可以看到插入的用戶記錄爲用戶名:leslie,密碼:123,在瀏覽器訪問http://localhost:8080/demo
,我們這裏就不定製登錄頁,用默認生成的:
訪問成功
Security的配置類(使用加密方式):
現實開發中,出於安全性考慮,密碼通常需要使用加密的方式保存,即用戶在瀏覽器輸入密碼後,程序會自動加密密碼並保存到數據庫中,一般是通過加鹽(規則)的方式。這樣就算有人直接竊取的數據庫的信息也不知道用戶的密碼是什麼。Security爲我們提供了一個類BCryptPasswordEncoder
,通過這個類可以對明文密碼進行動態加鹽加密,這裏爲了方便,直接在測試類中編寫代碼,並添加用戶到數據庫中:
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Test
void contextLoads() {
User user = new User();
user.setUsername("lau");
user.setPassword(new BCryptPasswordEncoder().encode("123"));
userMapper.addUser(user);
}
接下來打開數據庫可視工具,可以看到添加成功了,並且密碼是加密過後的(明文123):
同時在關聯表給他一個角色信息:
接着需要在配置類中改動一下代碼,其實就是剛剛的:
@Bean
NoOpPasswordEncoder noOpPasswordEncoder(){
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
換成這個即可:
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
最後重啓服務器,還是在瀏覽器中輸入測試:
成功~,後面一種加密方式用的較爲多。 感謝你的觀看.