技术栈:SpringBoot + Mybatis + SpringSecurity
程序地址:https://github.com/yaokuku123/spring-security
功能:实现基础的访问控制功能。通过注册在数据库中的用户,角色信息,实现认证和授权的功能
1. 目录结构
image-20210224104648367.png2. MySQL建表
2.1 构建MySQL数据库
/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 8.0.16 : Database - security_authority
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*Table structure for table `sys_permission` */
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`permission_NAME` varchar(30) DEFAULT NULL COMMENT '菜单名称',
`permission_url` varchar(100) DEFAULT NULL COMMENT '菜单地址',
`parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父菜单id',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `sys_permission` */
/*Table structure for table `sys_role` */
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`ROLE_NAME` varchar(30) DEFAULT NULL COMMENT '角色名称',
`ROLE_DESC` varchar(60) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*Data for the table `sys_role` */
/*Table structure for table `sys_role_permission` */
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`RID` int(11) NOT NULL COMMENT '角色编号',
`PID` int(11) NOT NULL COMMENT '权限编号',
PRIMARY KEY (`RID`,`PID`),
KEY `FK_Reference_12` (`PID`),
CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `sys_permission` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `sys_role_permission` */
/*Table structure for table `sys_user` */
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`password` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`status` int(1) DEFAULT '1' COMMENT '1开启0关闭',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `sys_user` */
/*Table structure for table `sys_user_role` */
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`,`RID`),
KEY `FK_Reference_10` (`RID`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `sys_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `sys_user_role` */
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
2.2 在MySQL的表中添加几组数据用于测试
说明:
-
用户:xiaoming
密码:199748
拥有的角色:ROLE_USER,ROLE_PRODUCT
-
用户:xiaoma
密码:199748
拥有的角色:ROLE_USER,ROLE_ORDER
# sys_user表 password使用加密的方式存储 两者的密码明文为 199748
+----+----------+--------------------------------------------------------------+--------+
| id | username | password | status |
+----+----------+--------------------------------------------------------------+--------+
| 4 | xiaoming | $2a$10$9fSu8H/o/qKhRYm8N3IrGePdu5Kj3QNujaW5whHGyoi8ta0Bj4SSG | 1 |
| 5 | xiaoma | $2a$10$NJkRs/2AoD1iHlLNe4LwPu8M1ZvmVp4lsCD0QEqCoaRg1Jn2AG2hu | 1 |
+----+----------+--------------------------------------------------------------+--------+
# sys_role表
+----+--------------+--------------+
| ID | ROLE_NAME | ROLE_DESC |
+----+--------------+--------------+
| 6 | ROLE_USER | Basic Role |
| 7 | ROLE_PRODUCT | Product Role |
| 8 | ROLE_ORDER | Order Role |
| 9 | ROLE_ADMIN | Root |
+----+--------------+--------------+
# sys_user_role表
+-----+-----+
| UID | RID |
+-----+-----+
| 4 | 6 |
| 5 | 6 |
| 4 | 7 |
| 5 | 8 |
+-----+-----+
3. 页面部分说明
3.1 templates文件夹存放使用Thymeleaf模板引擎的动态资源文件
- index.html,默认登录认证成功后的页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Spring Security</title>
</head>
<body>
<h1>Success</h1>
<a th:href="@{/product}">产品资源</a><br>
<a th:href="@{/order}">订单资源</a>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
</body>
</html>
- product.html,产品资源文件,授权给拥有ROLE_PRODUCT角色的用户
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>product</title>
</head>
<body class="container">
<h1 th:text="产品资源">product</h1>
<a th:href="@{/}">返回</a>
</body>
</html>
- order.html,订单资源文件,授权给拥有ROLE_ORDER角色的用户
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>product</title>
</head>
<body class="container">
<h1 th:text="订单资源">order</h1>
<a th:href="@{/}">返回</a>
</body>
</html>
3.2 static文件夹中存放的静态资源文件
- 403.html 403错误页面,用于处理来自403权限不足错误的请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>403</title>
</head>
<body>
<h1>403 Error</h1>
<a href="/">返回</a>
</body>
</html>
- 500.html 500错误页面,用于简单处理其他错误的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>500</title>
</head>
<body>
<h1>500 Error</h1>
<a href="/">返回</a>
</body>
</html>
4. 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yqj</groupId>
<artifactId>springboot-springsecurity</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</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-security</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5. yaml文件完成Spring的相关配置
application.yaml文件
server:
port: 8080
spring:
datasource:
username: root
password: 199748
url: jdbc:mysql:///security_authority
driver-class-name: com.mysql.jdbc.Driver
mybatis:
type-aliases-package: com.yqj.domain
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.yqj: debug
6. 业务逻辑相关内容的编写
6.1 SpringBoot启动类
SecurityApplication文件
package com.yqj;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: SecurityApplication
* Author: yaoqijun
* Date: 2021/2/24 10:13
*/
@SpringBootApplication
@MapperScan("com.yqj.mapper")
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class,args);
}
}
6.2 用户和角色类 com.yqj.springsecurity.domain
- SysUser用户类
package com.yqj.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: SysUser
* Author: yaoqijun
* Date: 2021/2/24 10:14
*/
@Data
public class SysUser implements UserDetails {
private Integer id;
private String username;
private String password;
private List<SysRole> roles;
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
- SysRole角色类
package com.yqj.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: SysRole
* Author: yaoqijun
* Date: 2021/2/24 10:15
*/
@Data
public class SysRole implements GrantedAuthority {
private Integer id;
private String roleName;
private String roleDesc;
@JsonIgnore
@Override
public String getAuthority() {
return roleName;
}
}
6.3 数据库访问层 com.yqj.springsecurity.mapper
- UserMapper,用户访问层接口
package com.yqj.mapper;
import com.yqj.domain.SysUser;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: UserMapper
* Author: yaoqijun
* Date: 2021/2/24 10:17
*/
public interface UserMapper {
@Select("select * from sys_user where username=#{username}")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "roles", column = "id", javaType = List.class,
many = @Many(select = "com.yqj.mapper.RoleMapper.findByUid"))
})
public SysUser findByName(String username);
}
- RoleMapper,角色访问层接口
package com.yqj.mapper;
import com.yqj.domain.SysRole;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: RoleMapper
* Author: yaoqijun
* Date: 2021/2/24 10:18
*/
public interface RoleMapper {
@Select(" select r.id,r.role_name roleName,r.role_desc roleDesc " +
" from sys_role r,sys_user_role ur " +
" where r.id=ur.rid and ur.uid=#{uid} ")
public List<SysRole> findByUid(Integer uid);
}
6.4 服务层 com.yqj.springsecurity.service
- UserService,继承SpringSecurity中的用于认证的类UserDetailsService
package com.yqj.service;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: UserService
* Author: yaoqijun
* Date: 2021/2/24 10:18
*/
public interface UserService extends UserDetailsService {
}
- UserServiceImpl,接口的实现类
package com.yqj.service.impl;
import com.yqj.mapper.UserMapper;
import com.yqj.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: UserServiceImpl
* Author: yaoqijun
* Date: 2021/2/24 10:19
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userMapper.findByName(username);
}
}
6.5 控制层 com.yqj.springsecurity.controller
SecurityController,用于对请求路径进行转发,从而可以访问由模板引擎渲染的动态资源
package com.yqj.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: SecurityController
* Author: yaoqijun
* Date: 2021/2/24 10:20
*/
@Controller
public class SecurityController {
@RequestMapping("/")
public String login() {
return "index";
}
@Secured("ROLE_PRODUCT")
@RequestMapping("/product")
public String product() {
return "product";
}
@Secured("ROLE_ORDER")
@RequestMapping("/order")
public String learning() {
return "order";
}
}
6.6 配置类
- SecurityConfig,配置SpringSecurity相关内容
package com.yqj.config;
import com.yqj.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: SecurityConfig
* Author: yaoqijun
* Date: 2021/2/24 10:21
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").hasAnyRole("USER","ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.successForwardUrl("/")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.permitAll()
.and()
.csrf().disable();
}
}
- ControllerExceptionHandler,用于处理错误请求的情况
package com.yqj.controller;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* Copyright(C),2019-2021,XXX公司
* FileName: ControllerExceptionHandler
* Author: yaoqijun
* Date: 2021/2/24 10:26
*/
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public String handlerException(RuntimeException e){
if(e instanceof AccessDeniedException){
//重定向到静态页面
return "redirect:/403.html";
}else {
return "redirect:/500.html";
}
}
}
7 效果说明
- 使用xiaoming认证成功登录后,可以成功访问产品资源,但是无法访问订单资源
- 使用xiaoma认证成功登录后,可以成功访问订单资源,但是无法访问产品资源
网友评论