SpringSecurity 安全控件使用指南,建议细读
获取 UserDetails 类,该类中包含用户认证相关等信息。
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
3、UserDetails 类
UserDetails 是 SpringSecurity 的一个核心接口。其中定义了一些可以获取用户名,密码、权限等与认证相关的信息的方法。SpringSecurity 内部使用 UserDetails 实现类大都是内置的 User 类,要使用 UserDetails,也可以直接使用该类。在 SpringSecurity 内部,很多需要使用用户信息的时候,基本上都是使用 UserDetails,比如在登录认证的时候。
通常需要在应用中获取当前用户的其他信息,如 E-mail、电话等。这时存放在 Authentication 中的 principal 只包含认证相关信息的 UserDetails 对象可能就不能满足我们的要求了。这时可以实现自己的 UserDetails,在该实现类中可以定义一些获取用户其他信息的方法,这样将来就可以直接从当前 SecurityContext 的 Authentication 的 principal 中获取这些信息。
UserDetails 是通过 UserDetailsService 的 loadUserByUsername()方法进行加载的,UserDetailsService 也是一个接口,我们也需要实现自己的 UserDetailsService 来加载自定义的 UserDetails 信息。
新建用户表的 Service 类实现 UserDetailsService 接口,来重写 UserDetailsService 的 loadUserByUsername()方法,根据用户名查询当前用户信息并返回,返回的类必须继承 Security 内置的 User 类。
@Service
public class SysUserService implements UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
}
}
4、UserDetailsService 类
Authentication.getPrincipal()?的返回类型是 Object。
Object getPrincipal();
但很多情况下返回的其实是一个 UserDetails 的实例。登录认证的时候 SpringSecurity 会通过 UserDetailsService 的 loadUserByUsername()方法获取对应的 UserDetails 进行认证,认证通过后会将该 UserDetails 赋给认证通过的 Authentication 的 Principal,然后在把该 Authentication 存入 SecurityContext。之后如果需要使用用户信息,可以通过 SecurityContextHolder 获取存放在 SecurityContext 中的 Authentication 的 principal,转为 UserDetails 类。
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
5、GrantedAuthority 类
Authentication 的 getAuthorities()方法可以返回当前 Authentication 对象用户的所有的权限。
Collection<? extends GrantedAuthority> getAuthorities();
即当前用户拥有的权限。其返回值是一个 GrantedAuthority 类型的数组,每一个 GrantedAuthority 对象代表赋予给当前用户的一种权限。GrantedAuthority 是一个接口,其通常是通过 UserDetailsService 进行加载,然后赋予 UserDetails 的。
GrantedAuthority 中只定义了一个 getAuthority()方法,该方法返回一个字符串,表示对应的权限,如果对应权限不能用字符串表示,则返回 null
获取当前用户的所有权限,存放到 List 集合中。
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<String> roleCodes=new ArrayList<>();
for (GrantedAuthority authority : userDetails.getAuthorities()) {
roleCodes.add(authority.getAuthority());
}
6、DaoAuthenticationProvider 类
SpringSecurity 默认了会使用 DaoAuthenticationProvider 实现 AuthenticationProvider 接口,专门进行用户认证的处理。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名,密码和所拥有的权限等。如果需要改变认证的方式,开发者可以实现自己的 AuthenticationProvider。
7、PasswordEncoder 类
在 SpringSecurity 中,对密码的加密都是由 PasswordEncoder 来完成的。在 SpringSecurity 中,已经对 PasswordEncoder 有了很多实现,包括 md5 加密,SHA-256 加密等,开发者只需要直接拿来用就可以。在 DaoAuthenticationProvider 中,有一个就是 PasswordEncoder 熟悉,密码加密功能主义靠它来完成。
SpringSecurity 的验证机制
1、SpringSecurity 的验证机制
SpringSecurity 大体上是由一堆 Filter 实现的,Filter 会在 SpringMVC 前拦截请求。Filter 包括登出 Filter(LogoutFilter)、用户名密码验证 Filter(UsernamePasswordAuthenticationFilter)之类。Filter 在交由其他组件完成细分的功能,最常用的 UsernamePasswordAuthenticationFilter 会持有一个 AuthenticationManager 引用,AuthenticationManager 是一个验证管理器,专门负责验证。但 AuthenticationManager 本身并不做具体的验证工作,AuthenticationManager 持有一个 AuthenticationProvider 集合,AuthenticationProvider 才是做验证工作的组件,验证成功或失败之后调用对应的 Handler。
搭建项目
1、新建 Springboot 项目
创建一个 SpringBoot 项目,添加 SpringSecurity 和 Thymeleaf 依赖。
pom.xml 完整代码。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<groupId>com.mcy</groupId>
<artifactId>security-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</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.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、项目结构。
编写代码
1、登录页面
用于测试的 HTML 页面,用了 bootstrap 的样式文件。
login.html 登录页面,主要有一个 form 表单和账号密码输入框,用于向 login 请求提交 username 和 password,从而进行登录。Security 默认登录地址 login,退出地址为 logout。代码如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Security 登录</title>
<link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body>
<div class="panel panel-primary" style="width: 500px; margin: 5% auto;">
<div class="panel-heading">登录</div>
<div class="panel-body">
<form action="login" method="post" class="form-horizontal">
<div th:if="${param.error != null}">
<div class="alert alert-danger">
<p color="red">用户名或密码错误!</p>
</div>
</div>
<div th:if="${param.logout != null}">
<div class="alert alert-success">
<p color="red">注销成功!</p>
</div>
</div>
//sec:authorize 可以判断用户是否登录,用户权限,设置该菜单是否显示
<p sec:authorize="isAnonymous()">未登录显示</p>
<p sec:authorize="isAuthenticated()">登录显示</p>
<div class="input-group input-sm">
<label class="input-group-addon"><i class="glyphicon glyphicon-user"></i></label>
<input type="text" class="form-control" name="username" placeholder="请输入用户名">
</div>
<div class="input-group input-sm">
<label class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></label>
<input type="password" class="form-control" name="password" placeholder="请输入密码">
</div>
<div class="form-actions">
<input type="submit" class="btn btn-block btn-primary btn-default" value="登录">
</div>
</form>
</div>
</div>
</body>
</html>
用于用户名或密码错误提示。
<div th:if="${param.error != null}">
<div class="alert alert-danger">
<p color="red">用户名或密码错误!</p>
</div>
</div>
退出,注销提示。
<div th:if="${param.logout != null}">
<div class="alert alert-success">
<p color="red">注销成功!</p>
</div>
</div>
注意这些提示都是 SpringSecurity 里边自带的。
sec:authorize 用于判断用户是否登录,用户是否拥有哪些角色权限,一般在前台页面控制菜单是否显示。
2、登录成功页面
home.html 是 ROLE_USER 用户登录之后显示的页面,同时提供了一个超链接到 admin 页面,代码如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home 页面</title>
<link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Home 页面</h3>
</div>
</div>
<h3>
<p>欢迎[<span color="red" th:text="${user}">用户名</span>]访问 Home 页面!
您的权限是:<span color="red" th:text="${role}">权限</span></p>
<p><a href="admin">访问 admin 页面</a></p>
<p><a href="logout">安全退出</a></p>
</h3>
</body>
</html>
admin.html 是 ROLE_ADMIN 用户登录之后显示的页面,同时提供了一个到 dba 页面的超链接,代码如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Admin 页面</title>
<link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Admin 页面</h3>
</div>
</div>
<h3>
<p>欢迎[<span color="red" th:text="${user}">用户名</span>]访问 Admin 页面!
您的权限是:<span color="red" th:text="${role}">权限</span></p>
<p><a href="dba">访问 dba 页面</a></p>
<p><a href="logout">安全退出</a></p>
</h3>
</body>
</html>
dba.html 页面只是显示简单的欢迎语句,代码如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>dba 页面</title>
<link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">dba 页面</h3>
</div>
</div>
<h3>
<p>欢迎[<span color="red" th:text="${user}">用户名</span>]访问 dba 页面!
您的权限是:<span color="red" th:text="${role}">权限</span></p>
<p><a href="logout">安全退出</a></p>
<p sec:authorize="isAnonymous()">未登录显示</p>
<p sec:authorize="isAuthenticated()">登录显示</p>
<p sec:authorize="hasRole('ROLE_ADMIN')">权限包含 ROLE_ADMIN 显示</p>
<p sec:authorize="!hasRole('ROLE_ADMIN')">权限不包含 ROLE_ADMIN 登录显示</p>
</h3>
</body>
</html>
accessDenied.html 是访问拒绝页面,如果登录的用户没有权限访问该页面,会进行提示,代码如下。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>访问拒绝页面</title>
<link rel="stylesheet" href="static/bootstrap/dist/css/bootstrap.css">
</head>
<body>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">AccessDenied 页面</h3>
</div>
</div>
<h3>
<p>欢迎[<span color="red" th:text="${user}">用户名</span>],您没有权限访问页面!
您的权限是:<span color="red" th:text="${role}">权限</span></p>
<p><a href="logout">安全退出</a></p>
</h3>
</body>
</html>
3、Security 配置类
在项目中新建一个 security 包,在该包下新建一个 WebSecurityConfig 类,继承 WebSecurityConfigurerAdapter 类,用于处理 SpringSecurity 的用户认证和授权操作,设置页面访问权限。
configure(AuthenticationManagerBuilder auth)和 configure(HttpSecurity http)两个方法中分别打印了一句话,用于启动项目时的跟踪调试。
successHandler(new LoginSuccessHandle())用于处理登出成功之后的操作,LoginSuccessHandle 类用于处理不同用户跳转到不同页面。
具体代码如下。
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.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
用户认证操作
@param auth
@
throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("WebSecurityConfig configure(AuthenticationManagerBuilder auth) 方法被调用。。。。。。");
//添加用户,并给予权限
auth.inMemoryAuthentication().withUser("aaa").password("{noop}1234").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("ADMIN", "DBA");
}
/**
用户授权操作
@param http
@throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("WebSecurityConfig configure(HttpSecurity http) 方法被调用。。。。。。");
http.csrf().disable(); //安全器令牌
http.authorizeRequests() //开始请求权限配置。
.antMatchers("/login", "/static/**").permitAll()
.antMatchers("/", "/home").hasRole("USER")
.antMatchers("/admin/**").hasAnyRole("ADMIN", "DBA")
.anyRequest().authenticated() //其余所有的请求都需要认证(用户登录)之后才可以访问。
.and()
.formLogin() //开始设置登录操作
.loginPage("/login") //设置登录页面的访问地址
//.defaultSuccessUrl("/main")? //指定登录成功后转向的页面。
.successHandler(new LoginSuccessHandle()) //登录成功跳转,LoginSuccessHandle 处理不同权限跳转不同页面
.failureUrl("/login?error") //指定登录失败后转向的页面和传递的参数。
.and()
.logout().permitAll() //退出
.and()
.exceptionHandling().accessDeniedPage("/accessDenied"); //指定异常处理页面
}
}
4、认证成功处理类 LoginSuccessHandle
认证成功处理类 LoginSuccessHandle,实现了 AuthenticationSuccessHandler 接口,是 Spring 用来处理用户认证授权并跳转到指定 URL 的。
重新 onAuthenticationSuccess 方法,获取当前用户的权限,根据权限跳转到指定的 URL 路径,代码如下。
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import java.io.IOException;
import java.util.Set;
public class LoginSuccessHandle implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException {
//authentication.getAuthorities() 获取当前用户的权限
Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
//获取到登陆者的权限,然后做跳转
if (roles.contains("ROLE_ADMIN")){
response.sendRedirect("/admin");
return;
}else if (roles.contains("ROLE_USER")){
response.sendRedirect("/home");
return;
}else {
response.sendRedirect("/accessDenied");
}
}
}
5、测试控制器
新建一个 IndexController 控制器,提供响应 login,home,admin,dba,AccessDenied 请求的方法。每个方法通过 getUsername()方法获得当前认证用户的用户名,通过 getAuthorith()方法获取当前认证用户的权限,并设置到 ModelMap 当中。
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.ArrayList;
import java.util.List;
@Controller
public class IndexController {
@RequestMapping(value = {"/", "/login"}, method = RequestMethod.GET)
public String index(){
return "login";
}
@RequestMapping("/home")
public String homePage(ModelMap map){
map.put("user", getUsername());
map.put("role", getAuthority());
return "home";
}
@RequestMapping("/admin")
public String admin(ModelMap map){
map.put("user", getUsername());
map.put("role", getAuthority());
return "admin";
}
@RequestMapping("/dba")
public String dba(ModelMap map){
map.put("user", getUsername());
map.put("role", getAuthority());
return "dba";
}
@RequestMapping("/accessDenied")
public String accessDenied(ModelMap map){
map.put("user", getUsername());
map.put("role", getAuthority());
return "accessDenied";
}
/**
获取当前用户名称
@return
*/
private String getUsername(){
//获取当前用户名称
String username = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("username="+username);
return username;
}
private String getAuthority(){
//获得 Authentication 对象,表示用户认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List<String> roles = new ArrayList<>();
//将角色名称添加到 List 集合
for(GrantedAuthority a: authentication.getAuthorities()){
roles.add(a.getAuthority());
}
System.out.println("role="+roles);
return roles.toString();
}
}
6、测试应用
运行项目可以看到 WebSecurityConfig 类中重写的两个方法已经执行,说明自定义的用户认证和用户授权工作已经生效。
在浏览器中输入 URL 测试应用:
http://localhost:8080?“/”、“/login”、“/home”、“admin”等任何一个当前项目的请求都会被重定向到http://localhost:8080/login页面,因为没有登录,,用户没有访问权限。login 页面中,用 sec:authorize 写了两个标签一个是登录成功显示,一个是未登录显示,没有登录,就只显示了一个。sec:authorize 一般用于控制菜单是否显示,效果如图。
输入错误的账号密码,会有提示用户名或密码错误。如图。
输入用户名:aaa,密码:1234,登录,该用户是“ROLE_USER”,跳转到 home 页面,如图。
单击超链接“访问 admin 页面”,由于当前用户的权限只是“ROLE_USER”,不能访问 admin 页面,所以会跳转到访问拒绝页面,如图。
单击超链接“安全退出”,会退出到登录页面,登录页面提示“注销成功!”,如图。
输入用户名:admin,密码 admin,登录,该用户是“ROLE_USER”和“ROLE_DBA”权限,跳转到 admin 页面,如图。
单击超链接“访问 dba 页面”,由于当前用户的权限是“ROLE_ADMIN”和“ROLE_DBA”,可以访问 dba 页面,所以会跳转到 dba 页面,如图。在 dba 页面代码中,写了四行用 sec:authorize 判断是否显示的标签,两行判断是否登录,两行判断该用户是否有这个角色,条件成立才显示标签内容,效果如图。
通过测试可以看到,项目已经使用 SpringSecurity 实现了用户认证和用户授权。
上面的案例并没有把用户和权限存放到数据中,在实际开发中,用户和权限肯定是存放在数据库中,下面就来看看如何把数据存放到数据库中。
用户和权限保存在数据库中
1、修改后项目的目录结构。
2、添加依赖
在原有的项目中,修改 pom.xml 文件,添加 MySQL 和 JPA 依赖。
这里使用的是 JPA 操作数据库,对 JPA 不了解的可以访问Spring-Data-Jpa入门
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
3、配置基本属性。
在 src/main/resources 下找到 application.properties 文件,在该配置文件中配置数据源和 jpa 相关的属性,需要在数据库中先新建一个 security 数据库。
#数据源信息配置
#数据库地址,jpa 数据库名,需要在数据库中先建一个 jpa 数据库
spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8
#用户名
spring.datasource.username=root
#密码
spring.datasource.password=root
#链接数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#指定数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#配置在日志中显示 SQL 语句
spring.jpa.show-sql=true
#指定自动创建|更新|验证数据库表结构等配置,配置成 updata
#表示如果数据库中存在持久化类对应的表就不创建,不存在就创建对应的表
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.use-new-id-generator-mappings=false
4、创建用户类和角色权限实体类
SysUser 权限类,代码如下。
import javax.persistence.*;
@Entity
@Table(name = "sys_role")
public class SysRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
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;
}
}
SysUser 用户类
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "sys_user")
public class SysUser {
private Integer id;
private String name;
private String username;
private String password;
private List<SysRole> roles;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
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 getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@ManyToMany(cascade=CascadeType.REFRESH,fetch=FetchType.EAGER)
@JoinTable(name="sys_user_role",joinColumns=@JoinColumn(name="user_id"),inverseJoinColumns=@JoinColumn(name="role_id"))
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
}
SysUser 类用来保存用户数据,其中 username 表示用户名,password 表示密码,name 表示用户姓名,roles 表示用户权限的 List 集合,用户和权限的关系是多对多关系。
5、创建数据访问接口
在项目中新建一个 repository 包,在该包下新建一个 UserRepository 接口和 RoleRepository 接口,继承 JpaRepository 接口,UserRepository 接口中写一个根据用户名去查询的方法 findByUsername,遵循 Spring-Data-Jpa 命名规范,代码如下。
import com.mcy.securitydemo.entity.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<SysUser, Integer> {
//根据登录用户名查询用户
SysUser findByUsername(String username);
}
import com.mcy.securitydemo.entity.SysRole;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RoleRepository extends JpaRepository<SysRole, Integer> {
}
6、创建自定义服务类 Service
在项目中新建一个 service 包,在该包下新建一个 UserService 类和 RoleService 类,UserService 类实现了 UserDetailsService 接口,登录认证的时候 SpringSecurity 会通过 UserDetailsService 的 loadUserByUsername()方法获取对应的 UserDetails 进行认证。
UserService 类重写了 UserDetailsService 接口中的 loadUserByUsername()方法,在方法中调用持久层接口的 findByUsername 方法通过 JPA 进行数据库验证,传递的参数是页面接收到的 username。最后将获得的用户名,密码和权限保存到 org.springframework.security.core.userdetails.User 类中并返回,该 User 类是 SpringSecurity 内部的实现,专门用于保存用户名,密码,权限等与认证相关的信息。
UserService 类代码
import com.mcy.securitydemo.entity.SysRole;
import com.mcy.securitydemo.entity.SysUser;
import com.mcy.securitydemo.repository.UserRepository;
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.List;
/**
需要实现 UserDetailsService 接口
因为在 SpringSecurity 中配置的相关参数需要是 UserDetailsService 类的数据
*/
@Service
public class UserService implements UserDetailsService {
//注入持久层接口 UserRepository
@Autowired
private UserRepository userRepository;
/**
重写 UserDetailsService 接口中的 loadUserByUsername 方法,通过该方法查询对应的用户
返回对象 UserDetails 是 SpringSecurity 的一个核心接口。
其中定义了一些可以获取用户名,密码,权限等与认证相关信息的方法。
@param username
@return
@throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用持久层接口 findByUsername 方法查询用户。
SysUser user = userRepository.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
//创建 List 集合,用来保存用户权限,GrantedAuthority 对象代表赋予当前用户的权限
List<GrantedAuthority> authorities = new ArrayList<>();
//获得当前用户权限集合
List<SysRole> roles = user.getRoles();
for(SysRole role: roles){
//将关联对象 role 的 name 属性保存为用户的认证权限
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
//此处返回的是 org.springframework.security.core.userdetails.User 类,该类是 SpringSecurity 内部的实现
//org.springframework.security.core.userdetails.User 类实现了 UserDetails 接口
return new User(user.getUsername(), user.getPassword(), authorities);
}
//保存方法
public void save(SysUser user) {
userRepository.save(user);
}
}
RoleService 类代码
评论