一、自定义RBAC表实现认证
创建自定义的用户表,角色表和用户角色关系表
代码语言:javascript复制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项目,加入依赖
代码语言:javascript复制<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,配置数据库连接信息
代码语言:javascript复制spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
自定义类SysUser实体类
自定义类SysUser类代替Spring Security中的UserDetails类,实现UserDetails中的方法, 放在entity包中,同时新增实体类SysRole
代码语言:javascript复制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()
}
代码语言:javascript复制public class SysRole {
private Integer id;
private String name;
private String memo;
//此处省略getter/setter/toString方法
}
创建SysUserMapper接口
创建mapper包,新建SysUserMapper接口,新增insertSysUser(), selectByUser()方法
代码语言:javascript复制@Repository
public interface SysUserMapper {
int insertSysUser(SysUser user);
//根据账号名称,获取用户信息
SysUser selectSysUser(String username);
}
创建SysUserMapper.xml配置文件
在resource目录下新增mappers文件夹,新增SysUserMapper.xml配置文件
代码语言:javascript复制<?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配置
代码语言:javascript复制# MyBatis设置
mybatis.mapper-locations=classpath:/mappers/*.xml
mybatis.type-aliases-package=com.citi.entity
创建启动类MainApplication
,新增jdbcInit()方法,在启动程序时初始化数据库,即往sys_user表里添加用户,创建三个用户Peter,Thor,Stark分别属于3个角色ADMIN,USER,READ,容器每次启动都会执行创建用户的操作,只在第一次启动时创建用户即可,创建完成之后可以将@PostConstruct注释即可
代码语言:javascript复制@MapperScan("com.citi.mapper")
@SpringBootApplication
public 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接口,增加查询用户角色方法
代码语言:javascript复制@Repository
public interface SysRoleMapper {
List<SysRole> selectRoleByUser(Integer userId);
}
实现SysRoleMapper接口
在resources目录下的mappers文件夹中新增xml配置文件,实现Mapper接口中的查询方法
代码语言:javascript复制<?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实现类对象
代码语言:javascript复制@Service
public 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,自定义安全配置
代码语言:javascript复制@Configuration
@EnableWebSecurity
public 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页面
代码语言:javascript复制@RestController
public 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";
}
}
代码语言:javascript复制<!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页面可以直接访问,其他页面都需要权限验证
代码语言:javascript复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
重启启动应用,清楚浏览器缓存,在浏览器中输入localhost:8080,页面直接显示,不需要在进行登录操作,随意点击index页面的一个连接跳出认证页面
16.给URL配置角色访问权限,修改configure(HttpSecurity http)方法
代码语言:javascript复制@Override
protected 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页面均报错
当然也可以给一个角色配置多个页面访问权限
代码语言:javascript复制@Override
protected 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的个数。
目前为止,已经实现在自定义数据库表的情况下实现用户认证和权限的鉴别,并且通过了测试。