Springboot+Security+Mybatis+mysql实现权限安全认证

SpringSecurity是Spring下的一个安全框架,与shiro 类似,一般用于用户认证(Authentication)和用户授权(Authorization)两个部分,常与与SpringBoot相整合。

一、介绍

SpringSecurity 过滤器链
SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的各个进行说明:

1.WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。

2.SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。

3.HeaderWriterFilter:用于将头信息加入响应中。

4.CsrfFilter:用于处理跨站请求伪造。

5.LogoutFilter:用于处理退出登录。

6.UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。

7.DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。

8.BasicAuthenticationFilter:检测和处理 http basic 认证。

9.RequestCacheAwareFilter:用来处理请求的缓存。

10.SecurityContextHolderAwareRequestFilter:主要是包装请求对象request。

11.AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。

12.SessionManagementFilter:管理 session 的过滤器

13.ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常。

14.FilterSecurityInterceptor:可以看做过滤器链的出口。

15.RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。

流程说明
1.客户端发起一个请求,进入 Security 过滤器链。

2.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。

3.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。

4.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。

二、依赖引入

完整的pom文件如下:

<?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.4.1</version>
        <relativePath></relativePath> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <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.4</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.example.springsecurity.SpringSecurityApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

三、配置文件

# 开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
# 用非严格的 HTML
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=utf-8
spring.thymeleaf.servlet.content-type=text/html
spring.datasource.druid.url=jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.druid.username=root
spring.datasource.druid.password=sunyue
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=20
spring.datasource.druid.test-on-borrow=true
#springbootjdbc导入包不和以前一样
spring.datasource.druid.driver-class-name= com.mysql.cj.jdbc.Driver
mybatis.type-aliases-package=com.example.springsecurity.entity
mybatis.mapper-locations=classpath:mapper/*.xml
#打印数据库的操作
logging.level.com.example.springsecurity.dao=debug
#redis缓存
### 配置Redis
mybatis.configuration.cache-enabled=true
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=152.136.30.116
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=sunyue
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-idle=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000

四、Security 配置类

SecurityConfig:

package com.example.springsecurity;

import com.example.springsecurity.server.serverImpl.TestUserServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity    //注解开启Spring Security的功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //明文加密器,只需要在内存中有这个管理对象,如果不添加,从前端登录时会抛出异常Bad credentials(数据库操作需要这个bean,内存不需要,只需要将密码加密就可以)
    /*内置的PasswordEncoder实现列表
    NoOpPasswordEncoder(已废除)
    明文密码加密方式,该方式已被废除(不建议在生产环境使用),不过还是支持开发阶段测试Spring Security的时候使用。
    BCryptPasswordEncoder
    Argon2PasswordEncoder
    Pbkdf2PasswordEncoder
    SCryptPasswordEncoder */
    //使用以上四个方法都可以解密,但是数据库中得密码也是对应方法得加密(添加用户数据是需要password加密new BCryptPasswordEncoder().encode("123456"))
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();//passwordEncoder的实现类
    }

    //构造一个内存框架对象,获取数据库中的数据
/*    @Bean
    public UserDetailsService myUserDetailsService(){
        return new TestUserServerImpl();
    }*/
    //也可以自动注入
    @Autowired
    private TestUserServer testUserServer;

    //用户授权
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于内存来存储用户信息(需要加密不然会报错---Encoded password does not look like BCrypt)
        /*auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user").password(new BCryptPasswordEncoder().encode("password")).authorities("user").and()          //设置
                .withUser("admin").password(new BCryptPasswordEncoder().encode("password")).authorities("admin", "user");*/
        //基于数据库来存储用户信息
        //auth.userDetailsService(myUserDetailsService());
        auth.userDetailsService(testUserServer);
    }

    //用户权限认证
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //这是认证的请求
                .authorizeRequests()
                .antMatchers("/", "/home", "/login", "/index", "/error").permitAll()  //这些请求 不需要认证
                //hasRole和hasAuthority的区别:我们在调用 hasAuthority 方法时,如果数据是从数据库中查询出来的,这里的权限和数据库中保存一致即可,
                // 可以不加 ROLE_ 前缀。即数据库中存储的用户角色如果是 admin,这里就是 admin。
                //也就是说,使用 hasAuthority 更具有一致性,你不用考虑要不要加 ROLE_ 前缀,数据库什么样这里就是什么样!
                // 而 hasRole 则不同,代码里如果写的是 USER,框架会自动加上 ROLE_ 前缀,所以数据库就必须是 ROLE_USER
                .antMatchers("/user/**").hasRole("USER")       //user及以下路径,需要ROLE_USER角色权限
                .antMatchers("/admin/**").hasAuthority("admin")//admin及以下路径,需要admin权限
                .and()
                //loginPage定制自定义登录页,相当于/toLogin其实自动转到/login,loginProcessingUr指定为login-》toLogin(表单提交只能为login),加了这个需要接受用户名和密码,登录成功跳转  "/"
                //.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login").defaultSuccessUrl("/")
                .formLogin()//自带的login
                .and()
                .csrf().disable()//关闭csrf
                //等出路径为logout,登出成功跳转 "/"
                .logout().logoutUrl("/logout").logoutSuccessUrl("/")
                .and()
                .rememberMe().rememberMeParameter("rememberMe");
    }

    /**
     * 核心过滤器配置,更多使用ignoring()用来忽略对静态资源的控制
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web
                .ignoring()
                .antMatchers("/image/**");
    }
}

AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 会让Security 自动构建一个 AuthenticationManager;如果想要使用该功能你需要配置一个 UserDetailService 和 PasswordEncoder。UserDetailsService 用于在认证器中根据用户传过来的用户名查找一个用户, PasswordEncoder 用于密码的加密与比对,我们存储用户密码的时候用PasswordEncoder.encode() 加密存储,在认证器里会调用 PasswordEncoder.matches() 方法进行密码比对。如果重写了该方法,Security 会启用 DaoAuthenticationProvider 这个认证器,该认证就是先调用 UserDetailsService.loadUserByUsername 然后使用 PasswordEncoder.matches() 进行密码比对,如果认证成功成功则返回一个 Authentication 对象。

五、自定义的UserDetailService

TestUserServer:

package com.example.springsecurity.server.serverImpl;

import com.example.springsecurity.dao.TestUserMapper;
import com.example.springsecurity.entity.TestUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.ArrayList;
import java.util.List;

@Service
public class TestUserServer implements UserDetailsService {
    @Autowired
    private TestUserMapper testUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //username参数,是在登陆时,用户传递的表单数据username
        //主要读取数据库3个值 username password authorities
        TestUser testUser = testUserMapper.selectOne(username);
        String authorityName = testUser.getAuthority();
        //为了返回一个UserDetails 使用User
        List<GrantedAuthority> authorities = new ArrayList<>();
        GrantedAuthority authority = new SimpleGrantedAuthority(authorityName);
        authorities.add(authority);
        //这里的User 是这个包下的 org.springframework.security.core.userdetails.User;
        return new User(
                testUser.getUsername(),
                testUser.getPassword(),
                authorities);
    }

}

六、实体类和DAO等

实体类:

package com.example.springsecurity.entity;

import lombok.Data;
import lombok.experimental.Accessors;


import java.io.Serializable;
import java.util.Date;

@Data
@Accessors(chain = true)
public class TestUser implements Serializable {
    private Long id;
    private String username;
    private String password;
    private String authority;
    private Date created;
    private Date updated;
}

DAO

package com.example.springsecurity.dao;

import com.example.springsecurity.entity.TestUser;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface TestUserMapper {
    List<TestUser> findAll();

    TestUser selectOne(String username);

    void insert(TestUser testUser);

    void update(TestUser testUser);

    void delete(String id);
}

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.example.springsecurity.dao.TestUserMapper">

    <select id="findAll"  resultType="TestUser">
       select * from test_user
    </select>
    <select id="selectOne" resultType="TestUser">
       select * from test_user where username=#{username}
    </select>

    <insert id="insert">
        insert into test_user (username,password,authority,created,updated) value (#{username},#{password},#{authority},#{created},#{updated})
    </insert>
    <update id="update">
		update test_user set username = #{username},password=#{password},authority=#{authority} where id = #{id}
	</update>
    <delete id="delete">
		delete from test_user where id = #{id}
	</delete>
</mapper>

七、前台代码

1.index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
<head>
     <title>Spring Security Example</title>   
</head>
<body>
   <h1>Welcome!</h1>
<!--自带登录页-->
   <p><a th:href="@{/login}">登录</a></p>
<!--自定义登录页-->
   <!--<p><a th:href="@{/toLogin}">登录</a></p>-->
   <p><a th:href="@{/user}">用户</a></p>
   <p><a th:href="@{/admin}">管理员</a></p>
   <p><a th:href="@{/logout}">退出</a></p>
</body>
</html>

2.login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form th:action="@{/login}" method="post">
    <input type="text" name="username" placeholder="UserName"/>
    <input type="password" name="password" placeholder="PassWord"/>
    <input type="checkbox" name="rememberMe"/>
    <input type="submit" value="提交"/>
</form>
</body>
</html>

3.user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>user</title>
</head>
<body>
user
</body>
</html>

4.admin.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>admin</title>
</head>
<body>
admin
</body>
</html>

5.数据库sql文

/*
Navicat MySQL Data Transfer

Source Server         : sunyue
Source Server Version : 50724
Source Host           : localhost:3306
Source Database       : security

Target Server Type    : MYSQL
Target Server Version : 50724
File Encoding         : 65001

Date: 2021-01-13 23:33:57
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for test_user
-- ----------------------------
DROP TABLE IF EXISTS `test_user`;
CREATE TABLE `test_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `authority` varchar(100) DEFAULT NULL,
  `created` date DEFAULT NULL,
  `updated` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of test_user
-- ----------------------------
INSERT INTO `test_user` VALUES ('3', 'user', '$2a$10$Skjo8i3cSopkOtVsvfX5I.eOPCOFm2B/CD4t0VjUDXfTZk6aSAvia', 'ROLE_USER', '2021-01-12', '2021-01-12');
INSERT INTO `test_user` VALUES ('4', 'admin', '$2a$10$3rzQ1Pn.Onx9N/Dy6a5O8.TZfB/kqo/Z1UOj9udQl4ne0AZDaxn4O', 'admin', '2021-01-12', '2021-01-12');

总结:当数据库中authority字段为ROLE_USER则拥有USER角色权限,可以访问路径/user下,为admin则拥有
/admin下

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值