一、Spring Security概述

1,Spring Security简介

Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(Inversion of Control 控制反转),DI(Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security 拥有以下特性:

  • 对身份验证和授权的全面且可扩展的支持
  • 防御会话固定、点击劫持,跨站请求伪造等攻击
  • 支持 Servlet API 集成
  • 支持与 Spring Web MVC 集成

Spring、Spring Boot 和 Spring Security 三者的关系如下图所示:

java spring权限控制 spring security权限控制_mysql

2、Spring Security快速入门

2.1、引入依赖

<!--springboot整合security坐标-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

或者是在创建springboot项目时勾选

java spring权限控制 spring security权限控制_java_02

2.2、创建一个控制器

@RestController
public class HelloController {
    @GetMapping("hello")
    public String hello(){
        return "Hello Spring security";
    }
}

2.3、启动项目

访问:http://localhost:8080/hello 结果打开的是一个登录页面,其实这时候我们的请求已经被保护起来了,要想访问,需要先登录。

java spring权限控制 spring security权限控制_java_03

Spring Security 默认提供了一个用户名为 user 的用户,其密码在控制台可以找到

java spring权限控制 spring security权限控制_spring_04

四、Spring Security 认证配置

1、WebSecurityConfigurerAdapter

当然还可以通过配置类的方式进行配置,创建一个配置类去继承,实现自定义用户名密码登录

/**
 * Spring Security配置类
 * 在springboot2.7 后WebSecurityConfigurerAdapter弃用了,用2.5.4
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 对密码进行加密。123 是密码明文,现在 Spring Security 强制性要求『不允许明文存储密码』。
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
    }
}

springsecurity强制性要求必须使用密码加密器(PasswordEncoder)对原始密码(注册密码)进行加密。因此,如果忘记指定 PasswordEncoder 会导致执行时会出现 There is no PasswordEncoder mapped for the id "null" 异常。

这是因为我们在对密码加密的时候使用到了 BCryptPasswordEncoder 对象,而 Spring Security 在对密码比对的过程中不会『自己创建』加密器,因此,需要我们在 Spring IoC 容器中配置、创建好加密器的单例对象,以供它直接使用。

所以,我们还需要在容器中配置、创建加密器的单例对象(上面那个 new 理论上可以改造成注入),修改Spring securitry配置类

/**
 * Spring Security配置类
 * 在springboot2.7后WebSecurityConfigurerAdapter弃用了,用2.5.4
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 对密码进行加密。123 是密码明文,现在 Spring Security 强制性要求『不允许明文存储密码』。
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        //此方法是在代码中写死的,无法从数据库中获取,所以基本不适用
        auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
    }
       /**
     * 将PasswordEncoder注入到ioc容器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Spring Security 内置的 Password Encoder 有:

加密算法名称

PasswordEncoder

NOOP

NoOpPasswordEncoder.getInstance()

SHA256

new StandardPasswordEncoder()

BCRYPT(官方推荐)

new BCryptPasswordEncoder()

LDAP

new LdapShaPasswordEncoder()

PBKDF2

new Pbkdf2PasswordEncoder()

SCRYPT

new SCryptPasswordEncoder()

MD4

new Md4PasswordEncoder()

MD5

new MessageDigestPasswordEncoder(“MD5”)

SHA_1

new MessageDigestPasswordEncoder(“SHA-1”)

SHA_256

new MessageDigestPasswordEncoder(“SHA-256”)

2、从数据库中获取信息

  • 在pom中添加依赖
<!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- mysql-connector -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>
  • 在yml中配置
server:
  port: 8070
spring:
  # 配置数据源信息
  datasource:
    # 配置数据源类型
    type: com.zaxxer.hikari.HikariDataSource
    # 配置连接数据库信息
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    username: root
    password: 123456
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.woniu.entity
  configuration:
    # 配置MyBatis日志,执行sql的时候,将sql打印到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 在entity包下新建实体类Users,建议不要用User,因为UserDetails 接口中有一个实现类叫User,不止一次会导错包
package com.woniu.entity;

import lombok.Data;

import java.util.List;
@Data
public class Users {

   private Integer id;

   private String username;

   private String account;

   private String password;

   private List<String> anths;//该用户拥有的权限

}
  • 在数据库中新建表

    User表

    关联表

    权限表
  • 在mapper包下创建UsersMapper
package com.woniu.dao;

import com.woniu.entity.Users;

public interface UsersDao {
        /**
         * 通过账号获取用户信息
         * @param account 
         * @return
         */
        Users getUserInfoByAccount(String account);
}
  • 在resources下新建mapper包,创建UsersMapper.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.woniu.mapper.UsersMapper">
    <resultMap id="userMap" type="Users">
        <result column="id" property="id"></result>
        <result column="username" property="username"></result>
        <result column="account" property="account"></result>
        <result column="password" property="password"></result>
        <collection property="anths" ofType="java.lang.String">
            <result column="anth_code"></result>
        </collection>
    </resultMap>
    <select id="getUserInfoByAccount" resultMap="userMap">
        SELECT
            us.id,
            us.username,
            us.account,
            us.password,
            ta.anth_code
        FROM
            t_users us
                left join t_user_anth tua on us.id = tua.user_id
                left join t_anth ta on tua.anth_id = ta.id
        where account = #{account}
    </select>
</mapper>
  • 在service包下创建一个MyUserDetailsService 类实现UserDetailsService 接口
/**
 * spring security认证业务类
 */
@Service
public class MyUserDetailsService implements UserDetailsService {
   
    @Autowired
    private UsersMapper usersMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
           //调用dao到数据库中根据username查找用户信息
        Users users = usersMapper.getByUserName(username);
        String anths = String.join(",", userInfo.getAnths());
         try {
            //将查找到的用户帐号与密码保存到Security的User对象中由Security进行比对
            return new User(users.getUsername(), passwordEncoder.encode(users.getPassword()),
                    //配置登录用户有哪些角色和权限,此处模拟直接写死
                    AuthorityUtils.commaSeparatedStringToAuthorityList(anths);
        }catch (Exception e){
            throw  new UsernameNotFoundException("用户"+username+"不存在");
        }
    }
}
  • 修改spring security配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(userDetailsService);
    }
     
    /**
     * 将PasswordEncoder注入到ioc容器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • 启动项目,在浏览器中测试
  • java spring权限控制 spring security权限控制_spring boot_05