写点什么

Spring 全家桶之 Spring Security(三)

作者:小白
  • 2022 年 8 月 23 日
    上海
  • 本文字数:6985 字

    阅读完需:约 23 分钟

一、自定义 RBAC 表实现认证

创建自定义的用户表,角色表和用户角色关系表

SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;
-- ------------------------------ Table structure for sys_role-- ----------------------------DROP TABLE IF EXISTS `sys_role`;CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rolename` varchar(255) NOT NULL, `rolememo` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ------------------------------ Table structure for sys_user-- ----------------------------DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `realname` varchar(255) DEFAULT NULL, `isenable` varchar(255) NOT NULL, `islock` varchar(255) NOT NULL, `iscredentials` varchar(255) DEFAULT NULL, `createtime` datetime DEFAULT NULL, `logintime` datetime DEFAULT NULL, `isexpire` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
-- ------------------------------ Table structure for sys_user_role-- ----------------------------DROP TABLE IF EXISTS `sys_user_role`;CREATE TABLE `sys_user_role` ( `userid` int(11) DEFAULT NULL, `roleid` int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
复制代码

创建 Maven 项目,加入依赖

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.2.1.RELEASE</version>    <relativePath/> <!-- lookup parent from repository --></parent>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </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.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
复制代码

配置 application.properties,配置数据库连接信息

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghaispring.datasource.username=rootspring.datasource.password=root
复制代码

自定义类 SysUser 实体类

自定义类 SysUser 类代替 Spring Security 中的 UserDetails 类,实现 UserDetails 中的方法, 放在 entity 包中,同时新增实体类 SysRole


public class SysUser implements UserDetails {
private Integer id; private String username; private String password; private String realname; private boolean isExpired; private boolean isLocked; private boolean isCredentials; private boolean isEnabled;
private Date createTime; private Date loginTime;
private List<GrantedAuthority> authorities;

public SysUser(){
}
public SysUser(String username, String password, String realname, boolean isExpired, boolean isLocked, boolean isCredentials, boolean isEnabled, Date createTime, Date loginTime,List<GrantedAuthority> authorities) { this.username = username; this.password = password; this.realname = realname; this.isExpired = isExpired; this.isLocked = isLocked; this.isCredentials = isCredentials; this.isEnabled = isEnabled; this.createTime = createTime; this.loginTime = loginTime; this.authorities = authorities; } @Override public boolean isAccountNonExpired() { return isExpired; }
@Override public boolean isAccountNonLocked() { return isLocked; }
@Override public boolean isCredentialsNonExpired() { return isCredentials; }
@Override public boolean isEnabled() { return isEnabled; } // 此处省略getter/setter/toString()}
复制代码


public class SysRole {
private Integer id; private String name; private String memo; //此处省略getter/setter/toString方法}
复制代码

创建 SysUserMapper 接口

创建 mapper 包,新建 SysUserMapper 接口,新增 insertSysUser(), selectByUser()方法


@Repositorypublic interface SysUserMapper {
int insertSysUser(SysUser user); //根据账号名称,获取用户信息 SysUser selectSysUser(String username);}
复制代码

创建 SysUserMapper.xml 配置文件

在 resource 目录下新增 mappers 文件夹,新增 SysUserMapper.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.citi.mapper.SysUserMapper">
<resultMap id="userMapper" type="com.citi.entity.SysUser"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password" /> <result column="realname" property="realname" /> <result column="isenable" property="isEnabled" /> <result column="islock" property="isLocked" /> <result column="iscredentials" property="isCredentials" /> <result column="createtime" property="createTime" /> <result column="logintime" property="loginTime" /> <result column="isexpire" property="isExpired" /> </resultMap>
<insert id="insertSysUser"> insert into sys_user(username,password,realname, isenable,islock,iscredentials,createtime,logintime)
values(#{username},#{password},#{realname},#{isEnabled}, #{isLocked},#{isCredentials},#{createTime},#{loginTime}) </insert>

<select id="selectSysUser" resultMap="userMapper"> select id, username,password,realname,isexpire, isenable,islock,iscredentials,createtime,logintime from sys_user where username=#{username} </select></mapper>
复制代码

配置 MyBatis

在 application.properties 配置文件总增加 mybatis 配置


# MyBatis设置mybatis.mapper-locations=classpath:/mappers/*.xmlmybatis.type-aliases-package=com.citi.entity
复制代码

创建启动类 MainApplication

,新增 jdbcInit()方法,在启动程序时初始化数据库,即往 sys_user 表里添加用户,创建三个用户 Peter,Thor,Stark 分别属于 3 个角色 ADMIN,USER,READ,容器每次启动都会执行创建用户的操作,只在第一次启动时创建用户即可,创建完成之后可以将 @PostConstruct 注释即可


@MapperScan("com.citi.mapper")@SpringBootApplicationpublic class MainApplication {
@Resource private SysUserMapper sysUserMapper;
@PostConstruct public void jdbcInit(){ List<GrantedAuthority> authorityList = new ArrayList<>(); // 角色名称需要以ROLE_开头,后面加上自定义的角色名称 GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + "ADMIN"); authorityList.add(authority); // 密码加密 PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); SysUser user = new SysUser("Peter",passwordEncoder.encode("12345"),"Peter Parker",true,true,true, true, new Date(),new Date(),authorityList); sysUserMapper.insertSysUser(user); }
public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); }}
复制代码

往 sys_role,sys_user_role 中添加数据


新增 SysRoleMapper 接口

在 mapper 包中新增 SysRoleMapper 接口,增加查询用户角色方法


@Repositorypublic interface SysRoleMapper {
List<SysRole> selectRoleByUser(Integer userId);}
复制代码

实现 SysRoleMapper 接口

在 resources 目录下的 mappers 文件夹中新增 xml 配置文件,实现 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.citi.mapper.SysRoleMapper">
<resultMap id="roleMapper" type="com.citi.entity.SysRole"> <id column="id" property="id" /> <result column="rolename" property="name" /> <result column="rolememo" property="demo" /> </resultMap>
<select id="selectRoleByUser" parameterType="integer" resultMap="roleMapper"> SELECT r.id, r.rolename, rolememo FROM sys_user_role ur, sys_role r WHERE ur.roleid = r.id AND ur.userid = #{userId} </select></mapper>
复制代码

创建自定义的 UserDetailsService 实现类

重写方法中查询数据库获取用户信息,获取角色数据,构建 UserDetails 实现类对象


@Servicepublic class JdbcUserDetailsService implements UserDetailsService {
@Resource private SysUserMapper userMapper;
@Resource private SysRoleMapper roleMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1.根据username获取SysUser SysUser user = userMapper.selectSysUser(username); // 2.根据userId获取用户角色 if (user != null){ List<SysRole> sysRoles = roleMapper.selectRoleByUser(user.getId()); String roleName = ""; List<GrantedAuthority> authorities = new ArrayList<>();
for (SysRole sysRole : sysRoles) { roleName = sysRole.getName(); SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + roleName); authorities.add(grantedAuthority); }
return user; }
return user; }}
复制代码

新建 CustSecurityConfig 配置列

在 config 包下新建 CustSecurityConfig,自定义 WebSecurityConfigurerAdapter,自定义安全配置


@Configuration@EnableWebSecuritypublic class CustSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource private UserDetailsService userDetailsService;
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); }}
复制代码

新增 UserController

新增 controller 包,新增 UserController 及 index.html 页面


@RestControllerpublic class UserController {
@GetMapping(value = "/access/user", produces = "text/html;charset=utf-8") public String user(){ return "USER"; }
@GetMapping(value = "/access/read", produces = "text/html;charset=utf-8") public String read(){ return "READ"; }
@GetMapping(value = "/access/admin", produces = "text/html;charset=utf-8") public String admin(){ return "ADMIN"; }}
复制代码


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8"/>    <title>Title</title></head><body><p>验证访问</p><a href="/access/user">USER</a><a href="/access/read">READ</a><a href="/access/admin">ADMIN</a></body></html>
复制代码

启动 MainApplication

启动 MainApplication,打开 localhost:8080,自动跳转至 localhost:8080/login



输入用户名密码 Thor/12345,Peter/12345,Stark/12345,可以成功跳转至 index 页面



给 index 页面去除权限控制,在 CustSecurityConfig 类中新增重载的 configure 方法,出了 index 页面可以直接访问,其他页面都需要权限验证


@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html").permitAll() .anyRequest().authenticated() .and() .formLogin();}
复制代码


重启启动应用,清楚浏览器缓存,在浏览器中输入 localhost:8080,页面直接显示,不需要在进行登录操作,随意点击 index 页面的一个连接跳出认证页面



16.给 URL 配置角色访问权限,修改 configure(HttpSecurity http)方法


@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html").permitAll() // 给url配置角色访问权限 .antMatchers("/access/user").hasRole("USER") .antMatchers("/access/read").hasRole("READ") .antMatchers("/access/admin").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin();}
复制代码


以上代码给 USER 角色配置了/access/user 的访问权限,给 READ 角色配置了/access/read 的访问权限,给 ADMIN 角色配置了/access/admin 的访问权限


17.验证权限,重新启动应用,打开首页如下,为保证正确性,首先清楚浏览器缓存,三个用户 Peter,Thor,Stark 分别属于 3 个角色 ADMIN,USER,READ,Thor 账户还拥有 ADMIN 的权限



首先验证 USER 角色,点击 USER 链接,输入 Thor/123456,正常访问



其他页面,访问异常



由于 Thor 账户还有 ADMIN 角色,所以 ADMIN 页面也是可以正常访问的



清楚浏览器缓存,使用 READ 角色的 Stark 账号访问,read 页面可以正常访问



试试其他页面,访问 user 页面和 admin 页面均报错




当然也可以给一个角色配置多个页面访问权限


@Overrideprotected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html").permitAll() // 给url配置角色访问权限 .antMatchers("/access/user","/access/read").hasRole("USER") .antMatchers("/access/read").hasRole("READ") .antMatchers("/access/admin","/access/read","/access/user").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin();}
复制代码


antMatchers()参数为 String...antPatterns,不限制 url 的个数。


目前为止,已经实现在自定义数据库表的情况下实现用户认证和权限的鉴别,并且通过了测试。

用户头像

小白

关注

QA 2019.08.05 加入

微信号JingnanSJ或者公众号RiemannHypo获取异步和图灵系列书籍

评论

发布
暂无评论
Spring 全家桶之 Spring Security(三)_8月月更_小白_InfoQ写作社区