文章目录
一、 环境准备
用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
用户授权指的是验证某个用户是否有权限执行某个操作。
spring security的主要核心功能为 认证和授权,所有的权限架构也是基于这两个核心功能去实现的。
“认证”,是为用户建立一个他所声明的主体。主题一般是指用户,设备或可以在你系统中执行动作的其他系统。
“授权”指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主题已经由 身份验证过程建立了。
导入依赖
# SpringBoot版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<!-- SpringSecurity 权限 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--SpringBootweb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!--JPA-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
创建数据库
一般权限控制有三层,即:用户<–>角色<–>权限,用户与角色是多对多,角色和权限也是多对多。
用户表、角色表以及一个中间表
建库SQL语句:
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户表';
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';
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 COMMENT='用户与角色关联表';
# 初始化数据
INSERT INTO `sys_role` VALUES ('1', 'ROLE_ADMIN');
INSERT INTO `sys_role` VALUES ('2', 'ROLE_USER');
INSERT INTO `sys_user` VALUES ('1', 'admin', '123');
INSERT INTO `sys_user` VALUES ('2', 'zs', '123');
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '2');
注意:
1.用户的登陆认证是由Spring Security进行处理的,请求路径默认为/login,用户名字段默认为username,密码字段默认为password
2.这里的权限格式为ROLE_XXX,是Spring Security规定的。
准备页面
在resources跟目录下创建index.html和home.html两个页面,登录页面是index,登录成功以后会跳转home页面。
index.html页面我使用了Bootstrap样式。
Bootstrap中文网官网样式下载
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<!--<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">-->
<head>
<meta charset="utf-8">
<meta name="description" content="SpringSecurityDemo">
<meta name="author" content="LiuShihao">
<title>SpringSecurityLogin</title>
<!-- 不需要加上static 目录-->
<link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="/css/signin.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
<form class="form-signin" action="/login" method="post">
<h2 class="form-signin-heading">SpringSecurity</h2>
<input type="text" name="username" class="form-control" placeholder="Username" required autofocus>
<br><input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">登 录</button>
</form>
</div>
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="zh-CN">
<!--<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">-->
<head>
<meta charset="utf-8">
<meta name="description" content="练习使用SpringSecurity">
<meta name="author" content="LiuShihao">
<title>HOME</title>
<link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="/css/signin.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
<h1>登录成功</h1>
<a href="/admin">检测ROLE_ADMIN角色</a><hr><br>
<a href="/user">检测ROLE_USER角色</a><hr><br>
<button onclick="window.location.href='/logout'">退出登录</button>
</div>
</body>
</html>
yml配置文件
server:
port: 9002
spring:
application:
name: SpringSecurityDemo
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://116.62.13.104:3306/tensquare_user?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF8
username: root
password: 123456
jpa: # SpringDataJPA的配置
database: mysql
show-sql: true
# springboot默认访问static,resources,public这些文件夹下的文件,而没有默认访问templates下的
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/
thymeleaf:
cache: false
实体类
@Data
@Entity(name = "sys_role")
public class SysRole {
@Id
private Integer id;
private String name;
}
@Data
@Entity(name = "sys_user")
public class SysUser {
@Id
private Integer id;
private String name;
private String password;
}
@Data
@Entity(name = "sys_user_role")
public class SysUserRole {
@Id
private Integer userId;
private Integer roleId;
}
数据访问层
角色表
public interface SysRoleRepository extends JpaRepository<SysRole,Integer> {
/**
* 根据 角色id查询角色
* @param id
* @return
*/
SysRole findSysRolesById (Integer id);
/**
* 根绝角色name 查询角色
* @param name
* @return
*/
SysRole findSysRoleByName(String name);
}
用户表
public interface SysUserRepository extends JpaRepository<SysUser,Integer> {
SysUser findSysUsersByName(String name);
}
用户角色中间表
public interface SysUserRoleRepository extends JpaRepository<SysUserRole,Integer> {
List<SysUserRole> findSysUserRolesByUserId(int userID);
}
业务层
public interface SysService {
/**
* 根据用户名查询用户
* @param username
* @return
*/
SysUser findUserByUsername(String username);
/**
* 根据用户ID 查询角色信息ID
* @param id
* @return
*/
List<SysUserRole> findRolesIdByUserID(Integer id);
/**
* 根据角色ID 查询角色
* @param roleId
* @return
*/
SysRole findRoleByRoleId(Integer roleId);
}
业务层实现类
@Service
public class SysServiceImpl implements SysService {
/**
* 用户表
*/
@Autowired
SysUserRepository sysUserRepository;
/**
* 角色表
*/
@Autowired
SysRoleRepository sysRoleRepository;
/**
* 用户角色中间表
*/
@Autowired
SysUserRoleRepository sysUserRoleRepository;
/**
* 根据用户名查询用户信息 查询用户表
* @param username
* @return
*/
@Override
public SysUser findUserByUsername(String username) {
SysUser sysUser = sysUserRepository.findSysUsersByName(username);
return sysUser;
}
/**
* 根据用户ID查询角色ID 查询用户角色中间表
* @param id
* @return
*/
@Override
public List<SysUserRole> findRolesIdByUserID(Integer id) {
List<SysUserRole> sysUserRolesByUserId = sysUserRoleRepository.findSysUserRolesByUserId(id);
return sysUserRolesByUserId;
}
/**
* 根据角色ID查询角色信息 查询角色表
* @param roleId
* @return
*/
@Override
public SysRole findRoleByRoleId(Integer roleId) {
return sysRoleRepository.findSysRolesById(roleId);
}
/**
* 根据角色Name查询角色信息 查询角色表
* @param roleName
* @return
*/
@Override
public SysRole findRoleIdBuRoleName(String roleName) {
return sysRoleRepository.findSysRoleByName(roleName);
}
二、UserDetailsService
UserDetailsService是连接我们的数据库和springsecurity的桥梁。
首先我们需要自定义 UserDetailsService ,将用户信息和权限注入进来。
我们需要重写 loadUserByUsername 方法,参数是用户输入的用户名。返回值是UserDetails,这是一个接口,一般使用它的子类org.springframework.security.core.userdetails.User,它有三个参数,分别是用户名、密码和权限集。
代码逻辑:
根据登陆页面传入的username查询用户信息,如果存在,则根据用户ID查询角色ID(用户角色中间表),(返回的是集合,因为一个用户可能对应多个角色,是一对多的关系),再遍历集合,根据角色ID查询角色信息,最后返回用户名、密码和权限集。
package com.lsh.service.Impl;
import com.lsh.model.SysRole;
import com.lsh.model.SysUser;
import com.lsh.model.SysUserRole;
import com.lsh.service.SysService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author :LiuShihao
* @date :Created in 2020/11/9 3:09 下午
* @desc :自定义的进行认证的具体实现类
* * 根据用户名查出对应的角色,将角色信息交给SpringSecurity托管
* * 在访问对应资源时就会自动去验证该用户是否有权限访问
*/
// 注入时按名字注入
@Service("userDetailsService")
@Slf4j
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
SysService sysService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据 username 查询数据库 用户信息
SysUser user = sysService.findUserByUsername(username);
log.info("用户:"+user);
// 判断用户是否存在
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 存在用户 然后根据用户ID查询所有角色信息Id
List<SysUserRole> rolesId = sysService.findRolesIdByUserID(user.getId());
// 根据从[用户角色中间表]中查出的角色信息(也就是角色id),
// 去角色表中查询全部角色名
// 并将其角色信息放入GrantedAuthority集合
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (SysUserRole sysUserRole : rolesId) {
SysRole role = sysService.findRoleByRoleId(sysUserRole.getRoleId());
log.info("角色:"+role);
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
/**
* 返回值是UserDetails,这是一个接口,
* 一般使用它的子类org.springframework.security.core.userdetails.User,
* 它有三个参数,分别是用户名、密码和权限集
*/
return new User(user.getName(), user.getPassword(), authorities);
}
}
三、SpringSecurityConfig
该类是 Spring Security 的配置类,该类的三个注解分别是标识该类是配置类、开启 Security 服务、开启全局 Securtiy 注解。
首先将我们自定义的 userDetailsService 注入进来,在 configure() 方法中使用 auth.userDetailsService() 方法替换掉默认的 userDetailsService。
package com.lsh.config;
import com.lsh.service.Impl.MyUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author :LiuShihao
* @date :Created in 2020/11/3 10:45 上午
* @desc :
*/
// 配置类注解
@Configuration
// 开启 Security 服务
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 注入自定义认证类
*/
@Autowired
@Qualifier("userDetailsService")
private MyUserDetailsServiceImpl userDetailsService;
/**
* 密码的转码解码
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}
/**
* 定义具体的路径资源对应的权限
* authorizeRequests所有security全注解配置实现的开端,表示开始说明需要的权限。
* 需要的权限分两部分,第一部分是拦截的路径,第二部分访问该路径需要的权限。
* antMatchers表示拦截什么路径,permitAll任何权限都可以访问,直接放行所有。
* anyRequest()任何的请求,authenticated认证后才能访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
.antMatchers("/anon").permitAll()
// /admin资源需要ROLE_ADMIN角色才能访问
.antMatchers("/admin").hasRole("ADMIN")
// /user 资源需要有ROLE_USER才能访问
.antMatchers("/user").hasRole("USER")
//释放静态资源
.antMatchers("/js/**","/css/**","/images/*","/fonts/**","/**/*.png","/**/*.jpg").permitAll()
// 所有请求都需要认证后才能访问
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
.logout().permitAll();
// 关闭CSRF跨域
http.csrf().disable();
}
/**
* 设置拦截忽略文件夹,可以对静态资源放行 与上边的一样 用那个都可以:
* @param web
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略URL
web.ignoring().antMatchers("/**/*.js", "/image/*.jepg", "/**/*.css", "/**/*.js", "/**/*.map", "/**/*.html",
"/**/*.png");
}
}
Bug CSS样式丢失
原因:样式引用路径写错或者css被SpringSecurity拦截器拦截。
解决方法:
第一:引用路径写错。
第二:在SpringSecurityConfig释放样式
// 配置类注解
@Configuration
// 开启 Security 服务
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 注入自定义认证类
*/
@Autowired
@Qualifier("userDetailsService")
private MyUserDetailsServiceImpl userDetailsService;
/**
* 密码的转码解码
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}
/**
* 定义具体的路径资源对应的权限
* authorizeRequests所有security全注解配置实现的开端,表示开始说明需要的权限。
* 需要的权限分两部分,第一部分是拦截的路径,第二部分访问该路径需要的权限。
* antMatchers表示拦截什么路径,permitAll任何权限都可以访问,直接放行所有。
* anyRequest()任何的请求,authenticated认证后才能访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
.antMatchers("/anon").permitAll()
// /admin资源需要ROLE_ADMIN角色才能访问
.antMatchers("/admin").hasRole("ADMIN")
// /user 资源需要有ROLE_USER才能访问
.antMatchers("/user").hasRole("USER")
//释放静态资源
.antMatchers("/js/**","/css/**","/images/*","/fonts/**","/**/*.png","/**/*.jpg").permitAll()
// 所有请求都需要认证后才能访问
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
.logout().permitAll();
// 关闭CSRF跨域
http.csrf().disable();
}
/**
* 设置拦截忽略文件夹,可以对静态资源放行 与上边的一样 用那个都可以:
* @param web
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略URL
web.ignoring().antMatchers("/**/*.js", "/image/*.jepg", "/**/*.css", "/**/*.js", "/**/*.map", "/**/*.html",
"/**/*.png");
}
}
代码已上传码源仓库:Gitee仓库地址