SpringBoot整合SpringSecurity
本章源码地址:https://gitee.com/lin8081/LWH10
下一章案例:Springboot整合SpringSecurity二
1.基本介绍
SpringSecurity概念
- SpringSecurity是一个安全管理框架,提供了认证与授权这些基本操作
- 认证: 用户访问系统,系统校验用户身份是否合法的过程就是认证。常见的认证: 登陆认证。
- 授权:用户认证后,访问系统资源,校验用户是否有权限访问系统资源的过程就是授权访问校验,简称为授权。权限校验过程:1.获取用户的权限; 2. 知道访问资源需要的权限;3.拿着访问资源需要的权限去用户权限列表查找,找到则授权访问。否则拒绝访问。
2.创建数据库表
一般权限控制有三层,即:用户
<–>角色
<–>权限
,用户与角色是多对多,角色和权限也是多对多。这里我们先暂时不考虑权限,只考虑用户
<–>角色
。 这里为了测试,表结构简单设计,后续可以根据业务添加先关字段。
数据库:Mysql 5.6 创建表结构:
-- 用户表
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL COMMENT '账号',
`password` varchar(255) NOT NULL COMMENT '密码',
`true_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(50) DEFAULT NULL COMMENT '手机',
`email` varchar(25) DEFAULT NULL COMMENT '邮箱',
`createtime` varchar(25) DEFAULT NULL COMMENT '创建时间',
`status` int(2) DEFAULT NULL COMMENT '状态',
`remarks` varchar(1000) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
-- 角色表
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL,
`name` varchar(50) DEFAULT NULL COMMENT '角色名',
`roleDesc` varchar(50) DEFAULT NULL COMMENT '角色说明',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-- 用户-角色关系表
CREATE TABLE `sys_user_role` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
KEY `fk_role_id` (`role_id`),
CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
初始化数据:
insert into `sys_role` values ('1', 'ROLE_ADMIN','管理员');
insert into `sys_role` values ('2', 'ROLE_USER','普通用户');
insert into `sys_user` (id, username,password) values ('1', 'admin', '123');
insert into `sys_user` (id, username,password) values ('2', 'user', '123');
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '2');
**注意:**这里的权限格式为ROLE_XXX
,是Spring Security规定的,不要乱起名字。
3.准备页面
用于登录的 login.html
片段以及用户登录成功后跳转的 index.html
,将其放置在工程 resources/static
目录下:
login.html片段:
<form method="post" action="/login">
<!-- 账号 -->
<div class="layui-form-item">
<label class="layui-form-label">账号</label>
<div class="layui-input-block">
<input name="username" id="userName" value="admin" placeholder="默认账号:admin" type="text" lay-verify="required" class="layui-input">
</div>
</div>
<!-- 密码 -->
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-block">
<input name="password" id="password" value="123" placeholder="默认密码:111111" type="password" lay-verify="required" class="layui-input">
</div>
</div>
<div>
<button type="submit" class="layui-btn layui-btn-fluid" >登 录</button>
</div>
</form>
首页index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登陆成功</h1>
<a href="/admin">检测ROLE_ADMIN角色</a>
<a href="/user">检测ROLE_USER角色</a>
<button onclick="window.location.href='/logout'">退出登录</button>
</body>
</html>
4.导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/>
</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
</dependencies>
5.配置application.yml
server:
port: 9000
spring:
application:
name: auth01
datasource:
druid:
url: jdbc:mysql://localhost:3306/auth01?useUnicode=true&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC&characterEncoding=utf8
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
initialSize: 5 #初始建立连接数量
minIdle: 5 #最小连接数量
maxActive: 20 #最大连接数量
maxWait: 10000 #获取连接最大等待时间,毫秒
testOnBorrow: true #申请连接时检测连接是否有效
testOnReturn: false #归还连接时检测连接是否有效
timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒)
# mybatis
mybatis:
#config-location: classpath:mybatis/mybatis-config.xml # mybatis配置文件位置
mapper-locations:
- classpath*:mapper/**/*.xml # mapper映射文件位置
type-aliases-package: com.lwh.model # 别名包
configuration:
cache-enabled: true
6.创建实体类、DAO、Service和Controller
实体类:
-
SysUser:
public class SysUser implements Serializable{ private static final long serialVersionUID = -2836223054703407171L; private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
-
SysRole:
public class SysRole implements Serializable { private static final long serialVersionUID = 7510551869226022669L; private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
SysUserRole:
public class SysUserRole implements Serializable{ private static final long serialVersionUID = -3256750757278740295L; private Integer userId; private Integer roleId; public SysUserRole() { } public SysUserRole(Integer userId, Integer roleId) { this.userId = userId; this.roleId = roleId; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } }
DAO:
-
SysUserMapper:
@Mapper public interface SysUserMapper { SysUser selectById(Integer id); SysUser selectByName(String username); }
-
SysRoleMapper:
@Mapper public interface SysRoleMapper { SysRole selectById(Integer id); }
-
SysUserRoleMapper:
@Mapper public interface SysUserRoleMapper { List<SysUserRole> listByUserId(Integer userId); }
Service:
-
SysUserService:
接口
public interface SysUserService { SysUser selectById(Integer id); SysUser selectByName(String name); }
实现类
@Service public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserMapper userMapper; @Override public SysUser selectById(Integer id) { return userMapper.selectById(id); } @Override public SysUser selectByName(String name) { return userMapper.selectByName(name); } }
-
SysRoleService:
接口
public interface SysRoleService { public SysRole selectById(Integer id); }
实现类
@Service public class SysRoleServiceImpl implements SysRoleService { @Autowired private SysRoleMapper roleMapper; @Override public SysRole selectById(Integer id) { return roleMapper.selectById(id); } }
-
SysUserRoleService:
接口
public interface SysUserRoleService { List<SysUserRole> listByUserId(Integer userId); }
实现类
@Service public class SysUserRoleServiceImpl implements SysUserRoleService { @Autowired private SysUserRoleMapper userRoleMapper; @Override public List<SysUserRole> listByUserId(Integer userId) { return userRoleMapper.listByUserId(userId); } }
Controller:
@Controller
public class LoginController {
private Logger logger = LoggerFactory.getLogger(LoginController.class);
@GetMapping("/login")
public String showLogin() {
return "/pages/login.html";
}
@GetMapping("/")
public String showHome() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
logger.info("当前登陆用户:" + username);
return "/pages/index.html";
}
@GetMapping("/admin")
@ResponseBody
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String printAdmin() {
return "如果你看见这句话,说明你有ROLE_ADMIN角色";
}
@GetMapping("/user")
@ResponseBody
@PreAuthorize("hasRole('ROLE_USER')")
public String printUser() {
return "如果你看见这句话,说明你有ROLE_USER角色";
}
}
注意:如代码所示:
- 获取当前登录用户:
SecurityContextHolder.getContext().getAuthentication()
@PreAuthorize
用于判断用户是否有指定权限,没有就不能访问
7.配置mapper.xml
UserMapper.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.lwh.dao.SysUserMapper" >
<resultMap id="BaseResultMap" type="com.lwh.model.user.SysUser" >
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<result column="true_name" property="trueName" jdbcType="VARCHAR"/>
<result column="remarks" property="remarks" jdbcType="VARCHAR"/>
<result column="phone" property="phone" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List" >
id,username,password,true_name,remarks,phone
</sql>
<select id="selectById" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM sys_user WHERE id = #{id}
</select>
<select id="selectByName" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM sys_user WHERE username = #{username}
</select>
</mapper>
RoleMapper.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.lwh.dao.SysRoleMapper" >
<resultMap id="BaseResultMap" type="com.lwh.model.user.SysRole" >
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List" >
id,name
</sql>
<select id="selectById" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM sys_role WHERE id = #{id}
</select>
</mapper>
UserRoleMapper.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.lwh.dao.SysUserRoleMapper" >
<resultMap id="BaseResultMap" type="com.lwh.model.user.SysUserRole" >
<id column="user_id" property="userId" jdbcType="INTEGER"/>
<id column="role_id" property="roleId" jdbcType="INTEGER"/>
</resultMap>
<sql id="Base_Column_List" >
user_id,role_id
</sql>
<select id="listByUserId" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM sys_user_role WHERE user_id = #{userId}
</select>
</mapper>
8.配置SpringSecurity
1.UserDetailService
自定义 UserDetailService
, 将用户信息和权限注入进来。
CustomUserDetailsService:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private SysUserService userService;
@Autowired
private SysRoleService roleService;
@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 = roleService.selectById(userRole.getRoleId());
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
// 返回UserDetails实现类
return new User(user.getUsername(), user.getPassword(), authorities);
}
}
2.WebSecurityConfig
Spring Security默认是禁用注解的,要想开启注解, 需要在继承WebSecurityConfigurerAdapter
的类上加@EnableGlobalMethodSecurity
注解, 来判断用户对某个控制层的方法是否具有访问权限
例如:上面代码方法前不加@preAuthorize注解,意味着所有用户都能访问方法,如果加上注解,表示只要具备指定角色的用户才有权限访问。也就是得继承WebSecurityConfigurerAdapter
类并加上@EnableGlobalMethodSecurity
注解才能在控制层加@preAuthorize注解
@EnableGlobalMethodSecurity详解:
@EnableGlobalMethodSecurity(securedEnabled=true)
开启@Secured
注解过滤权限@EnableGlobalMethodSecurity(jsr250Enabled=true)
开启@RolesAllowed
注解过滤权限@EnableGlobalMethodSecurity(prePostEnabled=true)
使用表达式实现方法级别的安全性 ,4个注解可用:@PreAuthorize
在方法调用之前,基于表达式的计算结果来限制对方法的访问@PostAuthorize
允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常@PostFilter
允许方法调用,但必须按照表达式来过滤方法的结果@PreFilter
允许方法调用,但必须在进入方法之前过滤输入值
首先,我们将自定义的 userDetailsService
注入进来,在 configure()
方法中使用 auth.userDetailsService()
方法替换掉默认的 userDetailsService
。
这里我们还指定了密码的加密模式(5.0版本强制要求设置),我们采用SpringSecurity提供的加密模式:BCryptPasswordEncoder
,它帮我们实现了PasswordEncoder
,当然也可以自定义加密模式。
- WebSecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
// .antMatchers().permitAll()
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
.logout().permitAll();
// 关闭CSRF跨域
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers(
"/**/**.gif",
"/**/**.jpg",
"/**/**.css",
"/**/**.jq",
"/**/**.js",
"/**/**.ttf",
"/**/**.woff",
"/**/**.woff2",
"/**/**.png");
}
}
9.运行测试
1.启动工程之前,由于数据库用户表的密码初始化的是明文,这里我们需要使用SpringSecurity 提供的加密工具类对密码进行重新加密修改。
加密类:
public class SpringSecurityUtil {
public static void main(String[] args) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String password = bCryptPasswordEncoder.encode("123");
System.out.println(password);
}
}
加密后密文:$2a 10 10 10z/GksRmh.CxJbP3nAiFWUu0yLfJ6YvUL04OxttZXBnClvEbU3KQgy
注:随机盐加密所以两次加密后结果不一样
2.启动工程:
ROLE_ADMIN 账户:用户名: admin,密码: 123
ROLE_USER 账户:用户名: user,密码: 123
3.网页显示