漫步SpringSecurity---在SpringBoot中整合Security技術並實現數據庫認證

最近在做一些練習的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>

數據庫準備(建表語句省略)

數據庫設計,權限認證無非就是用戶和角色,一個用戶可以擁有多個角色,一個角色可以屬於多個用戶。所以它們是典型的多對多關係。數據庫表可以這樣子

  1. 有一張用戶表users(user_id,username,password,enabled,locked):
    在這裏插入圖片描述

  2. 角色表roles():
    在這裏插入圖片描述

  3. 它們的關聯表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();
    }

最後重啓服務器,還是在瀏覽器中輸入測試:
在這裏插入圖片描述在這裏插入圖片描述
成功~,後面一種加密方式用的較爲多。 感謝你的觀看.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章