写点什么

SpringSecurity 安全控件使用指南,建议细读

作者:Java高工P7
  • 2021 年 11 月 10 日
  • 本文字数:11836 字

    阅读完需:约 39 分钟

获取 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

  • @


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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 类代码

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
SpringSecurity安全控件使用指南,建议细读