在前面 SpringBoot 2.7.2 的系列文章中,已經建立了幾個 computer 相關的介面,這些介面直接通過 Spring Doc 或 POSTMAN 就可以存取。例如:
GET http://localhost:9099/computer/1
存取該服務可以獲取 id 為 1 的電腦詳情。
接下來的文章就使用 Spring Security 實現使用者認證和授權。
Spring Boot 對 Spring Security 非常友好,它已經管理了 Spring Security 的依賴版本,並且提供 starter 的方式進行整合:
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
新增依賴後重新啟動服務,可以在控制檯看到輸出:
在瀏覽器中存取該服務,會自動跳轉到登入頁面,該頁面是 Spring Security 預設提供的。瀏覽器中位址列自動跳轉到:
http://localhost:9099/login
。
在登入頁面,使用者名稱填寫 user
,密碼填寫上面控制檯中輸出的密碼,點選「Sign in」,便會進入系統,顯示電腦詳情。
咱們使用的 Spring Boot 版本是 2.7.2,對應的 Spring Security 版本為 5.7.2,從 5.7 開始,Spring Security 的使用有一些改變,其中之一就是設定 Security 時不推薦繼承 WebSecurityConfigurerAdapter 類。
上面的 demo 中使用的使用者名稱是 user,密碼是在啟動中生成的,這肯定不符合開發的需求。設定使用者名稱密碼有三種方式:
前面兩者在 demo 中玩玩就行。
這種方式比較簡單,直接在 application.yml 中設定即可:
spring:
profiles:
active: @env@
security:
user:
name: hero1
password: '111111'
roles: 'admin'
刪除上面的 security 節點相關的設定,使用組態檔,設定在記憶體中生效的使用者名稱密碼。
建立設定類 SecurityConfig:
package com.yygnb.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.builder()
.username("hero2")
.password(passwordEncoder().encode("111111"))
.roles("admin")
.build();
return new InMemoryUserDetailsManager(user);
}
}
重啟服務,登入介面輸入 hero2/111111 進行測試。
這種方式也是寫死的使用者名稱密碼,在實際開發中不會使用的。所以,先刪除這個檔案,然後再繼續後面的學習。
在開始這個部分之前,請確保刪除了上面編寫的 SecurityConfig。
首先需要定義表結構。
由於我這裡是基於前面 spring boot 2.7.2 系列文章的demo進行的,我繼續沿用 Liquibase 的方式定義表結構。尊敬的讀者們可以從 GitHub 上獲取 demo 基礎工程,也可以聯絡我獲取,還可以脫離 demo 基礎工程,自己用習慣的方式建立資料庫表結構,如 SQL 語句、圖形化介面等。
在 resources/db/demo/
目錄中建立檔案 demo-changelog-v2.xml
,該檔案用來定義這次資料庫的變更。
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet id="T100-20220801-yyg-025" author="yyg">
<createTable remarks="使用者表" tableName="user">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="username" remarks="使用者名稱" type="VARCHAR(32)">
<constraints nullable="false"/>
</column>
<column name="password" remarks="密碼" type="VARCHAR(128)">
<constraints nullable="false"/>
</column>
<column name="name" remarks="姓名" type="VARCHAR(32)">
<constraints nullable="false"/>
</column>
</createTable>
<addAutoIncrement tableName="user" columnName="id" columnDataType="BIGINT"
incrementBy="1" startWith="1"/>
<insert tableName="user">
<column name="id" valueNumeric="1"/>
<column name="username" value="hero1"/>
<column name="password" value="111111"/>
<column name="name" value="HERO 1"/>
</insert>
<insert tableName="user">
<column name="id" valueNumeric="2"/>
<column name="username" value="hero2"/>
<column name="password" value="222222"/>
<column name="name" value="HERO 2"/>
</insert>
<insert tableName="user">
<column name="id" valueNumeric="3"/>
<column name="username" value="hero3"/>
<column name="password" value="333333"/>
<column name="name" value="HERO 3"/>
</insert>
</changeSet>
</databaseChangeLog>
該檔案定義了 user 表並插入了三條資料,user 表只有四個欄位:id 主鍵、使用者名稱、密碼、姓名,主鍵自增長。
重啟服務,會自動生成表結構。
可以通過逆向工程生成,可以複製下面的程式碼。
com.yygnb.demo.entity.User
/**
* 使用者表
*/
@Schema(title = "使用者")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(title = "使用者名稱")
@NotNull(message = "不能為空")
private String username;
@Schema(title = "密碼")
@NotNull(message = "不能為空")
private String password;
@Schema(title = "姓名")
private String name;
}
com.yygnb.demo.mapper.UserMapper
:
public interface UserMapper extends BaseMapper<User> {
}
resources/mapper/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.yygnb.demo.mapper.UserMapper">
</mapper>
到這一步,準備工作就完成了,接下來繼續回到 Spring Security。
Spring Security 中在認證授權時,需要 PasswordEncoder 物件,該物件可以對密碼加密。
com.yygnb.demo.config.PasswordEncoderConfig
:
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Spring Security 提供了一個介面:UserDetailsService 。該介面只有一個方法 loadUserByUsername,在該方法中就可以查詢資料庫,根據使用者名稱查詢使用者資訊了。該方法返回 UserDetails。UserDetails 是一個介面,Spring Security 提供了一個實現類 User。
建立類:UserDetailsServiceImpl,實現 UserDetailsService 介面,並新增 @Service
註解。
com.yygnb.demo.service.impl.UserDetailsServiceImpl
:
package com.yygnb.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yygnb.demo.entity.User;
import com.yygnb.demo.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
// @Autowired
private final PasswordEncoder passwordEncoder;
private final UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
User user = this.userMapper.selectOne(wrapper);
if (user == null) {
throw new UsernameNotFoundException("根據使用者名稱未查詢到使用者");
}
// 模擬查詢許可權
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new org.springframework.security.core.userdetails.User(
user.getUsername(), passwordEncoder.encode(user.getPassword()), authorities);
}
}
如果是以前版本的 Spring Security,還需要定義設定繼承自 WebSecurityConfigurerAdapter 類,重寫 config 方法,並在該方法中設定 UserDetailsService 等。現在的版本不需要這些無聊的操作了。
重啟服務,存取測試。
感謝你閱讀本文,如果本文給了你一點點幫助或者啟發,還請三連支援一下,點贊、關注、收藏,作者會持續與大家分享更多幹貨