使用SpringSecurity实现权限管理
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。本文通过区分用户及管理员两个权限,实现对这两角色不同的权限控制,使得不同权限的用户登录时显示不同的页面。
首先用户注册用户信息,输入账号、密码,选定身份,之后将用户信息存入数据库的两个表中,两个表通过CustomUserDetailService中的方法相关联,登陆后用户权限和管理员权限会显示不同的页面,而用户无权访问管理员可以访问的某些页面。
SpringSecyrity认证、授权流程:
用户访问资源→security拦截→跳转到login→提交表单→处理用户信息→借助UserDetailsService 获取用户信息→认证成功/失败
目录结构如下
1. pom.xml和application.yml
springboot使用的版本是2.3.1.RELEASE
1.1 pom.xml文件配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
1.2 application.yml
Spring:
dataSource:
username: root
password: password
url: jdbc:mysql://localhost:3306/salary?characterEncoding=utf-8&useUnicode=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
#联接MYSQL数据库
main:
allow-bean-definition-overriding: true
#所有bean允许重写
jpa:
database: MYSQL
show-sql: true
hibernate:
ddl-auto: update
thymeleaf:
mode: HTML
#开发引擎页面,修改要实时生效,1.禁用缓存, 2.页面修改完成后重新编译
cache: false
server:
port: 8088
#自定义端口
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.gui.entity
configuration:
map-underscore-to-camel-case: true
#mybatis配置文件
logging:
level:
org:
springframework:
security : INFO
#日志配置文件
2.创建数据库
数据库表有 用户表,角色表,用户角色关系表三张表:
插入数据库数据
insert into SYS_USER (id,username, password) values (1,'admin', '123456');
insert into SYS_USER (id,username, password) values (2,'user', '123456');
insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN');
insert into SYS_ROLE(id,name) values(2,'ROLE_USER');
//ROLE_ADMIN 和 ROLE_USER是SpringSecurity里的固定写法,可参照SpringSecurity中的源码
insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(1,1);
insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(2,2);
3.启动类UserApplication
@SpringBootApplication()
@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class })
@MapperScan("com.gui.mapper") //扫描mapper层
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserSalaryApplication.class, args);
}
}
4.编写实体类
实体类需要sysUser sysRole sysUserRole 三个实体类
/
*SysUser类 用户信息类
/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
//上面的注解帮助我们自动写出含参和无参的set、get方法。
//使用了eclipse中lombok的插件 下载详见https://www.cnblogs.com/romulus/p/11109610.html
public class SysUser implements Serializable{
static final long serialVersionUID = 1L;
//一般情况下,建议序列化的class都给一个序列化的ID,这样可以保证序列化的成功,版本的兼容性。
//详解请见 https://blog.csdn.net/u013325815/article/details/52041103
@Id
@GeneratedValue
private Integer id; //数据库中设置id自增
private String username; //账号
private String password; //密码
private String password1; //再次输入密码确认两次输入是否一致
}
/
*SysRole类 用户权限类
/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysRole {
@Id
private Integer id; //id自增
private String name; //权限
}
/
*SysUserRole类 关联SysUser和SysRole两个实体类
/
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUserRole implements Serializable{
static final long serialVersionUID = 1L;
@Id
private Integer id;
private Integer userId;
private Integer roleId;
}
5.编写mapper service serviceImpl层
5.1 SysUser对应的业务层如下
//SysUserMapper类
@Repository
public interface SysUserMapper {
public Integer insertSysUser(SysUser sysUser);
public SysUser selectByName(@Param("username") String username);
//@Param的作用是在Mybatis提供的作为dao层注解,传递参数的工具
public SysUser selectById(@Param("id") Integer id);
public Integer deleteSysUser(String username);
public Integer updateSysUser(SysUser sysUser);
public Integer insertInfoSysUser(SysUser sysUser);
public List<SysUser> selectSysUser();
}
<?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.gui.mapper.SalaryMapper">
<select id="listSalaryAll" resultType="com.gui.entity.Salary" parameterType="String">
select * from salary
</select>
<select id="selectSalaryById" parameterType="String" resultType="com.gui.entity.Salary">
select * from salary where id=#{id}
</select>
<delete id="deleteSalary" parameterType="String">
delete from salary where id=#{id}
</delete>
<insert id="insertSalary" parameterType="com.gui.entity.Salary">
insert into salary (id, name, sex, time, birth, salary, home) values (#{id}, #{name}, #{sex}, #{time}, #{birth}, #{salary}, #{home})
</insert>
<update id="updateSalary" parameterType="com.gui.entity.Salary">
update salary set id=#{id}, name=#{name}, sex=#{sex}, time=#{time}, birth=#{birth}, salary=#{salary}, home=#{home} where id=#{id}
</update>
</mapper>
@Service
public interface SysUserService {
public Integer insertUser(SysUser sysUser);
public SysUser selectByName(String username);
public SysUser selectById(Integer id);
public Integer deleteSysUser(String username);
public Integer updateSysUser(SysUser sysUser);
public Integer insertInfoSysUser(SysUser sysUser);
public List<SysUser> selectSysUser();
}
@Service
public class SysUserServiceImpl implements SysUserService{
@Autowired
private SysUserMapper sysUserMapper;
@Override
public Integer insertUser(SysUser sysUser) {
return sysUserMapper.insertSysUser(sysUser);
}
@Override
public SysUser selectByName(String username) {
return sysUserMapper.selectByName(username);
}
@Override
public SysUser selectById(Integer id) {
return sysUserMapper.selectById(id);
}
@Override
public Integer deleteSysUser(String username) {
return sysUserMapper.deleteSysUser(username);
}
@Override
public Integer updateSysUser(SysUser sysUser) {
return sysUserMapper.updateSysUser(sysUser);
}
@Override
public Integer insertInfoSysUser(SysUser sysUser) {
return sysUserMapper.insertInfoSysUser(sysUser);
}
@Override
public List<SysUser> selectSysUser() {
return sysUserMapper.selectSysUser();
}
}
5.2 sysRole对应的业务层
sysRole和sysUserRole层只列出mapper层,service层和serviceImpl过于简单 参照上面即可
SysRoleMapper类和SysRole.xml
@Repository
public interface SysRoleMapper {
public SysRole selectById(Integer id);
public Integer insertRole(String name);
public Integer updateRole(String name, Integer id);
}
<?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.gui.mapper.SysRoleMapper">
<select id="selectById" parameterType="Integer" resultType="com.gui.entity.SysRole">
select * from sys_role where id = #{id}
</select>
<insert id="insertRole" parameterType="string">
insert into sys_role (name) values (#{name})
</insert>
<update id="updateRole" parameterType="com.gui.entity.SysRole">
update sys_role set name = #{name} where id = #{id}
</update>
</mapper>
5.3 SysUserRole对应的业务层
SysUserRoleMapper类和SysUserRoleMapper.xml
@Repository
public interface SysUserRoleMapper {
List<SysUserRole> listByUserId(Integer userId);
public Integer sysUserRoleInsert(Integer user_id, Integer role_id);
}
<?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.gui.mapper.SysUserRoleMapper">
<select id="listByUserId" parameterType="Integer" resultType="com.gui.entity.SysUserRole">
select * from sys_user_role where id = #{id}
</select>
<insert id="sysUserRoleInsert">
insert into sys_user_role (user_id, role_id) values(#{user_id}, #{role_id})
</insert>
</mapper>
6.config层
6.1 MyMvcConfig
@Configuration
//表示配置类
public class MyMvcConfig implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//对视图控制器的快速配置 ViewControllerRegistry是视图控制器
registry.addViewController("/register.html").setViewName("register");
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/index.html").setViewName("index");
}
}
6.2 WebSecurityConfig
在SpringSecurity中,通过WebSecurityConfig类完成最关键的认证、授权配置。
configure(AuthenticationManagerBuilder auth)方法(身份验证管理器),调用UserDetailService 和 PasswordEncoder比对加密后密码的密码是否与数据库中密码匹配,如果匹配,则返回一个Authentication 对象。
configure(WebSecurity)用于影响全局安全性(配置资源,设置调试模式,通过实现自定义防火墙定义拒绝请求)的配置设置。
configure(HttpSecurity httpSecurity)方法用于实现业务层的功能,自定义登入登出,配置用户权限,匿名用户配置,增加CSRF支持等业务实现功能。
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//SpringSecurity主要做两件事,一件是认证,一件是授权。
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); //注册BCrypt加密类
}
@Bean
UserDetailsService customUserService(){
//注册UserDetailsService 的bean,实例化CustomUserDetailsService();
//该类实现UserDetailsServer接口,从数据库获取用户名,密码,角色权限
return new CustomUserDetailsService();
}
@Override
public void configure(WebSecurity web) throws Exception{
super.configure(web);
//configure(WebSecurity)用于影响全局安全性(配置资源,设置调试模式通过实现自定义防火墙定义拒绝请求)的配置设置。
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { //AuthenticationManagerBuilder身份验证管理器
auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
//把前端传来的数据加密
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception { //HTTP请求安全处理
httpSecurity
.authorizeRequests()
.antMatchers("/js/**").permitAll() //静态资源存放的位置
.antMatchers("/register.html","/register","/login","/login.html").permitAll()//登陆、注册页面,和跳转的URL, permitAll()无条件允许访问
.antMatchers("/index.html").hasAnyRole("ADMIN","USER") //hasAnyRole()用户拥有该权限可访问
.antMatchers("/UserSalary.html","/update.html","/insert.html").hasRole("ADMIN")
.anyRequest().authenticated() //任意匹配此url的用户需要身份验证
.and()
.formLogin()
.loginPage("/login.html") //未登录时跳转此页面
.loginProcessingUrl("/login")
.failureUrl("/login?error") //登陆失败时的跳转
.defaultSuccessUrl("/index.html") //登陆成功后默认跳转路径
.permitAll()
.and()
.rememberMe().tokenValiditySeconds(1209600).key("mykey")//设置浏览器的记忆remember功能
.and()
.logout()
.logoutSuccessUrl("/login?logout") //退出登录
.permitAll()
.and()
.csrf().disable(); //跨域保护
}
}
7. CustomUserDetailService
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
@Autowired
private SysUserService userService;
@Autowired
private SysRoleSecvice roleSecvice;
@Autowired
private SysUserRoleService userRoleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities = new ArrayList<>();
//从数据库中读取信息
SysUser user = userService.selectByName(username); //获得用户信息
//判断用户是否存在
if(user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
//添加权限
List<SysUserRole> userRoles = userRoleService.listByUserId(user.getId());
for(SysUserRole userRole : userRoles) {
SysRole role = roleSecvice.selectById(userRole.getRoleId());
authorities.add(new SimpleGrantedAuthority(role.getName()));//用authorities储存对应用户的权限
}
//返回UserDetails的实现类
String password = user.getPassword(); //得到加密后的密码
return new User(user.getUsername(), password, authorities);
}
}
8.Controller层
8.1 LoginController
@Controller
public class LoginController {
@RequestMapping("/login")
public String login() {
return "redirect:/index.html"; //认证通过后跳转到主页
}
}
8.2 RegisterController
@Controller
public class RegisterController {
@Autowired
private SysUserServiceImpl sysUserService;
@Autowired
private SysRoleServiceImpl sysRoleService;
@Autowired
private SysUserRoleService sysUserRoleService;
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@PostMapping("/register")
public String postRegisterController(SysUser sysUser, String name) {
String encodedPassword = passwordEncoder.encode(sysUser.getPassword().trim());
//将用户输入的密码加密
sysUser.setPassword(encodedPassword);
//将加密后的密码储存到sysUser中
sysUserService.insertUser(sysUser);
sysRoleService.insertRole(name); //向数据库中插入数据
Integer userId = sysUserService.selectByName(sysUser.getUsername()).getId();
//获得插入数据后的该列id
sysUserRoleService.sysUserRoleInsert(userId, userId);
//将id插入到数据库sys_user_role中
return "login.html";
}
}
9.登录页,注册页面,主页
9.1登录页login.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>用户登陆界面</title>
<link rel="stylesheet" th:href = "@{/js/layui/css/layui.css}">
<script th:src="@{/js/layui/layui.js}"></script>
</head>
<body>
<!-- 你的HTML代码 -->
<form class="layui-form" th:action="@{login}" method="post">
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 50px;">
<legend>登录</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">账号</label>
<div class="layui-input-inline">
<input type="username" name="username" required lay-verify="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
</div> <!-- lay-verify表单验证的关键字 -->
<i class="layui-icon" style="font-size: 30px; color: #1E9FFF;"></i>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-inline">
<input type="password" name="password" required lay-verify="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux"></div>
<i class="layui-icon layui-icon-password" style="font-size: 30px; color: #1E9FFF;"></i>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">登录</button> <!-- lay-filter类似于选择器 -->
<a href="register.html"><button type="button" class="layui-btn">注册</button></a>
</div>
</div>
</form>
<script>
</script>
</body>
</html>
9.2注册页面register.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>用户注册页面</title>
<link rel="stylesheet" th:href = "@{/js/layui/css/layui.css}">
</head>
<body>
<!-- 你的HTML代码 -->
<form class="layui-form" method="POST" th:action="@{register}">
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 50px;">
<legend>注册</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<input type="text" name="username" lay-verify="required" placeholder="请输入账号" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="password" name="password" lay-verify="required" placeholder="请输入密码" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="password" name="password1" lay-verify="required" placeholder="请再次输入密码" autocomplete="off" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">您的身份</label>
<div class="layui-input-block">
<input type="radio" name="name" value="ROLE_ADMIN" title="管理员">
<input type="radio" name="name" value="ROLE_USER" title="用户" checked>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
<a href="login.html"><button class="layui-btn">返回</button></a>
</div>
</div>
</form>
<script th:src="@{/js/layui/layui.js}"></script>
<script>
layui.use(['form'], function(){
form = layui.form;
});
</script>
</body>
</html>
9.3主页index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>首页</title>
<script th:src="@{/js/layui/layui.js}"></script>
<link rel="stylesheet" th:href = "@{/js/layui/css/layui.css}">
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin ">
<div class="layui-header">
<div class="layui-logo">员工信息管理</div>
<!-- 头部区域(可配合layui已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item">
<a href="javascript:;">
<span sec:authentication="name"></span>
</a>
</li>
<li class="layui-nav-item"><a href="logout">退出</a></li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<li class="layui-nav-item"><a href="index.html">首页</a></li>
<li class="layui-nav-item">
<a href="javascript:;">通知公告</a>
<dl class="layui-nav-child">
<dd><a href="announcement.html">全部公告</a></dd>
<dd><a href="myAnnouncement.html">我发布的</a></dd>
</dl>
</li>
<div sec:authorize="hasRole('ADMIN')">
<li class="layui-nav-item"><a href="UserSalary.html">员工信息表</a></li>
</div>
<div sec:authorize="hasRole('ADMIN')">
<li class="layui-nav-item"><a href="userInfo.html">用户信息表</a></li>
</div>
</ul>
</div>
</div>
<div class="layui-body">
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 50px;">
<legend>首页</legend>
</fieldset>
<div sec:authorize="${isAuthenticated()}">
欢迎<span sec:authentication="name"></span>
<br></br>
您的角色是:<span sec:authentication="principal.authorities"></span>
</div>
</div>
</div>
</body>
<script>
//注意:导航 依赖 element 模块,否则无法进行功能性操作
layui.use('element', function(){
var element = layui.element;
//…
});
</script>
</html>
10.页面展示
注册、登录页面:
index主页展示界面:
当以管理员身份登录时:
当以用户身份登录时:
如有错误请各位指正。
完整代码详见https://github.com/2638565363/springboot.git
本文参考自https://blog.csdn.net/u012373815/article/details/54632176
https://blog.csdn.net/wanderlustLee/article/details/80032177?utm_source=blogxgwz0