在阅读本篇文章前,笔者已默认读者拥有一定的Java开发基础与SpringBoot以及SpringSecurity框架使用经验,文章内容如有错误或者讲解不清之处,欢迎读者私信笔者交流!
前言
SpringSecurity框架是一个比较成熟的安全框架,主要用于用户的认证与权限管理。
本文主要通过尝试封装自定义User类实现对用户登录信息管理的拓展功能。
1. 引入依赖
由于SpringSecurity更新频繁,很多方法与配置方式常常处于即将废弃或不再支持状态,本人推荐读者与本人保持相同或相近的配置,或者了解自己写的没问题就可以。
1.1 SpringSecurity依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.5</version>
</dependency>
1.2 项目完整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>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.project</groupId>
<artifactId>test-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>monitor-server</name>
<description>monitor-server</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.37</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 自定义登录授权
本文通过继承UserDetailsService接口并实现loadUserByUsername方法提供自定义登录授权。
2.1 AccountService接口
package com.project.service;
import com.project.entity.dto.Account;
import com.baomidou.mybatisplus.extension.service.IService;
import com.project.entity.vo.request.ConfirmResetVo;
import com.project.entity.vo.request.CreateSubAccountVo;
import com.project.entity.vo.request.EmailResetVo;
import com.project.entity.vo.request.ModifyEmailVo;
import com.project.entity.vo.response.SubAccountVo;
import org.springframework.security.core.userdetails.UserDetailsService;
import java.util.List;
//这里由于使用mybatisplus框架,所以需要继承很多接口,实际上重点还是UserDetailsService接口
public interface AccountService extends IService<Account>, UserDetailsService {
//这是一个查询用户的方法,后续使用此方法可以查询用户信息
Account findAccountByUsernameOrEmail(String text);
}
2.2 AccountServiceImpl实现类
package com.project.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.project.entity.dto.Account;
import com.project.entity.dto.GrandUser;
import com.project.service.AccountService;
import com.project.mapper.AccountMapper;
import com.project.utils.FlowUtils;
import jakarta.annotation.Resource;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account>
implements AccountService {
//解码类,这里因为其他类里面已经注册为Bean,故直接依赖注入
@Resource
PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//这里是使用mybatisPlus框架实现用户查询,笔者也可以使用其他框架实现
Account account = this.findAccountByUsernameOrEmail(username);
if (account == null)
throw new UsernameNotFoundException("用户名或密码错误");
//关键就在这里,GrandUser类是什么鬼?
return GrandUser
.userBuilder()
.id(account.getId())
.email(account.getEmail())
.username(account.getUsername())
.password(account.getPassword())
.role(account.getRole())
.build();
}
@Override
public Account findAccountByUsernameOrEmail(String text) {
//mybatisPlus语法,应该比较好懂,不过这也不是本文需要关心的点
return this.query()
.eq("username", text)
.or()
.eq("email", text)
.one();
}
}
这里的关键就是GrandUser类了,本篇文章要讲解的封装类就是这个GrandUser,接下来让我们看看这个GrandUser类到底是什么鬼。
3. loadUserByUsername与UserDetails
在讲解GrandUser类之前,我们先来看看一种相对常见的loadUserByUsername方法实现。
示例代码如下所示:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = this.findAccountByNameOrEmail(username);
if(account == null)
throw new UsernameNotFoundException("用户名或密码错误");
return User
.withUsername(username)
.password(account.getPassword())
.roles(account.getRole())
.build();
}
我们可以看到,这个loadUserByUsername方法返回的应该是一个User类的对象,而且这种对象的创建方式是一种很明显的建造者模式(这里不懂什么是建造模式的强烈建议先去了解一下设计模式相关知识,否则后面肯定看不懂)。
而这个loadUserByUsername方法的签名上写明了要返回UserDetails类型。
这是一种什么类型呢?这种类型与User类又有什么关系呢?我们当然要进源码看一眼。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.security.core.userdetails;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
原来UserDetails是一个接口,这里我们先不用关心UserDetails到底是个什么接口,有什么功能,我们现在更应该看一看返回的User类又是什么鬼。(当然,Java基础好的同学肯定已经猜的八九不离十了)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.security.core.userdetails;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = 620L;
private static final Log logger = LogFactory.getLog(User.class);
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
}
public String getPassword() {
return this.password;
}
public String getUsername() {
return this.username;
}
public boolean isEnabled() {
return this.enabled;
}
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
public void eraseCredentials() {
this.password = null;
}
private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet(new AuthorityComparator());
Iterator var2 = authorities.iterator();
while(var2.hasNext()) {
GrantedAuthority grantedAuthority = (GrantedAuthority)var2.next();
Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
public boolean equals(Object obj) {
if (obj instanceof User user) {
return this.username.equals(user.getUsername());
} else {
return false;
}
}
public int hashCode() {
return this.username.hashCode();
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName()).append(" [");
sb.append("Username=").append(this.username).append(", ");
sb.append("Password=[PROTECTED], ");
sb.append("Enabled=").append(this.enabled).append(", ");
sb.append("AccountNonExpired=").append(this.accountNonExpired).append(", ");
sb.append("CredentialsNonExpired=").append(this.credentialsNonExpired).append(", ");
sb.append("AccountNonLocked=").append(this.accountNonLocked).append(", ");
sb.append("Granted Authorities=").append(this.authorities).append("]");
return sb.toString();
}
public static UserBuilder withUsername(String username) {
return builder().username(username);
}
public static UserBuilder builder() {
return new UserBuilder();
}
/** @deprecated */
@Deprecated
public static UserBuilder withDefaultPasswordEncoder() {
logger.warn("User.withDefaultPasswordEncoder() is considered unsafe for production and is only intended for sample applications.");
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserBuilder var10000 = builder();
Objects.requireNonNull(encoder);
return var10000.passwordEncoder(encoder::encode);
}
public static UserBuilder withUserDetails(UserDetails userDetails) {
return withUsername(userDetails.getUsername()).password(userDetails.getPassword()).accountExpired(!userDetails.isAccountNonExpired()).accountLocked(!userDetails.isAccountNonLocked()).authorities(userDetails.getAuthorities()).credentialsExpired(!userDetails.isCredentialsNonExpired()).disabled(!userDetails.isEnabled());
}
private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
private static final long serialVersionUID = 620L;
private AuthorityComparator() {
}
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
if (g2.getAuthority() == null) {
return -1;
} else {
return g1.getAuthority() == null ? 1 : g1.getAuthority().compareTo(g2.getAuthority());
}
}
}
public static final class UserBuilder {
private String username;
private String password;
private List<GrantedAuthority> authorities = new ArrayList();
private boolean accountExpired;
private boolean accountLocked;
private boolean credentialsExpired;
private boolean disabled;
private Function<String, String> passwordEncoder = (password) -> {
return password;
};
private UserBuilder() {
}
public UserBuilder username(String username) {
Assert.notNull(username, "username cannot be null");
this.username = username;
return this;
}
public UserBuilder password(String password) {
Assert.notNull(password, "password cannot be null");
this.password = password;
return this;
}
public UserBuilder passwordEncoder(Function<String, String> encoder) {
Assert.notNull(encoder, "encoder cannot be null");
this.passwordEncoder = encoder;
return this;
}
public UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList(roles.length);
String[] var3 = roles;
int var4 = roles.length;
for(int var5 = 0; var5 < var4; ++var5) {
String role = var3[var5];
Assert.isTrue(!role.startsWith("ROLE_"), () -> {
return role + " cannot start with ROLE_ (it is automatically added)";
});
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return this.authorities((Collection)authorities);
}
public UserBuilder authorities(GrantedAuthority... authorities) {
Assert.notNull(authorities, "authorities cannot be null");
return this.authorities((Collection)Arrays.asList(authorities));
}
public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "authorities cannot be null");
this.authorities = new ArrayList(authorities);
return this;
}
public UserBuilder authorities(String... authorities) {
Assert.notNull(authorities, "authorities cannot be null");
return this.authorities((Collection)AuthorityUtils.createAuthorityList(authorities));
}
public UserBuilder accountExpired(boolean accountExpired) {
this.accountExpired = accountExpired;
return this;
}
public UserBuilder accountLocked(boolean accountLocked) {
this.accountLocked = accountLocked;
return this;
}
public UserBuilder credentialsExpired(boolean credentialsExpired) {
this.credentialsExpired = credentialsExpired;
return this;
}
public UserBuilder disabled(boolean disabled) {
this.disabled = disabled;
return this;
}
public UserDetails build() {
String encodedPassword = (String)this.passwordEncoder.apply(this.password);
return new User(this.username, encodedPassword, !this.disabled, !this.accountExpired, !this.credentialsExpired, !this.accountLocked, this.authorities);
}
}
}
User类的代码洋洋洒洒大几百行,初学者一眼看上去肯定头都大了。没关系,源码本来就是这样,请跟上我的思路,现在不必过于关心代码体里面那一大堆成员与方法到底是干什么的。(当然,大佬随意)
我们最关心的问题是什么?当然是UserDetails与User类的关系。凭什么刚刚loadUserByUsername方法可以返回User类的对象呢?看方法签名我们就豁然开朗了,原来User类实现了UserDetails接口,一切是不是都合理起来了~
于是,本文的核心观点就出现了。我们是不是可以写一个实现UserDetails接口的类,然后在loadUserByUsername方法中返回呢?
答案是肯定的,不过我们完全可以基于User类进行改写,而不必从头写一个实现UserDetails接口的类,毕竟我们现在还没有看懂User类到底干了什么。
我们的目标很简单,在原有User类的基础上进行拓展,可以额外添加一些我们需要的字段或者方法,写出一个我们自己的User类。
笔者第一个想出来的方向是继承User类,并且也成功实现了想要的功能。不过在复习完设计模式后,笔者又感觉应该使用组合的方式实现,不过木已成舟,毕竟代码能跑起来就行(bushi
在了解笔者的改写思路后,读者可以尝试使用组合的方式,应该会更加轻松。
4. GrandUser类编写(本文核心内容)
我知道有很多喜欢自己钻研别人代码的同学和基础扎实的大佬,这里先把代码端上来罢(bushi
package com.project.entity.dto;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.util.Assert;
import java.util.*;
@Getter
public class GrandUser extends User {
private final Integer id;
private final String email;
private final String role;
public GrandUser(Integer id,String email,String username, String password,String role) {
this(id,email, username, password,toAuth(role));
}
public GrandUser(Integer id,String email,String username, String password,Collection<? extends GrantedAuthority> authorities) {
super(username, password,authorities);
this.id = id;
this.email = email;
this.role = toRole(authorities);
}
public static Collection<? extends GrantedAuthority> toAuth(String role) {
List<GrantedAuthority> authorities = new ArrayList<>();
Assert.isTrue(!role.startsWith("ROLE_"), () -> role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
return new ArrayList<>((Collection)authorities);
}
public String toRole(Collection<? extends GrantedAuthority> authorities) {
if (authorities == null) {
return null;
}
//得到authorities的第一个元素
GrantedAuthority authority = authorities.iterator().next();
if (authority.getAuthority().startsWith("ROLE_")) {
return authority.getAuthority().substring("ROLE_".length());
}
return null;
}
public static GrandUserBuilder userBuilder() {
return new GrandUserBuilder();
}
public static final class GrandUserBuilder {
private String username;
private String password;
private String role;
private String email;
private Integer id;
private List<GrantedAuthority> authorities = new ArrayList<>();
private GrandUserBuilder(){}
public GrandUserBuilder id(Integer id) {
this.id = id;
return this;
}
public GrandUserBuilder email(String email) {
this.email = email;
return this;
}
public GrandUserBuilder username(String username) {
this.username = username;
return this;
}
public GrandUserBuilder password(String password) {
this.password = password;
return this;
}
public GrandUserBuilder role(String role) {
this.role = role;
return this;
}
public GrandUserBuilder authorities(GrantedAuthority... authorities) {
Assert.notNull(authorities, "authorities cannot be null");
return this.authorities(Arrays.asList(authorities));
}
public GrandUserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "authorities cannot be null");
this.authorities = new ArrayList<>(authorities);
return this;
}
public GrandUserBuilder authorities(String... authorities) {
Assert.notNull(authorities, "authorities cannot be null");
return this.authorities(AuthorityUtils.createAuthorityList(authorities));
}
public GrandUser build() {
if(role != null)
return new GrandUser(id,email, username, password,role);
else if (authorities != null)
return new GrandUser(id,email, username, password,authorities);
else
return null;
}
}
}
我去,这代码真长吧。这是我能看懂的?相信很多看到这里的小伙伴会发出这种声音,还是那句话,跟上我的思路,准备发车了。我再明确一下本文的目标:
在原有User类的基础上进行拓展,可以额外添加一些我们需要的字段或者方法,写出一个我们自己的User类。
首先我们要添加一些额外的字段,于是GrandUser类开头定义了几个字段。
private final Integer id;
private final String email;
private final String role;
没毛病吧,字段看名字就知道是干啥的。这里有一个字段值得额外注意:role
User类构建的时候里面不是也有个roles方法吗,你这个字段role与User里的roles方法有什么关系呢?这里就让我告诉你一个真相:User类里面根本就没有roles或者role这个字段,只有一个roles方法
看到这里的笔者一定一头雾水,不是,你这还没给我解释清楚你这个role字段呢,User类的roles方法和字段又到底是什么鬼?
要解释清楚这个问题,现在我就要带着同学们开始看User类中的UserBuilder类的源码了,记住一句话,看不懂的时候就不要过于关注底层:
public static final class UserBuilder {
private String username;
private String password;
private List<GrantedAuthority> authorities = new ArrayList();
private boolean accountExpired;
private boolean accountLocked;
private boolean credentialsExpired;
private boolean disabled;
private Function<String, String> passwordEncoder = (password) -> {
return password;
};
private UserBuilder() {
}
public UserBuilder username(String username) {
Assert.notNull(username, "username cannot be null");
this.username = username;
return this;
}
public UserBuilder password(String password) {
Assert.notNull(password, "password cannot be null");
this.password = password;
return this;
}
public UserBuilder passwordEncoder(Function<String, String> encoder) {
Assert.notNull(encoder, "encoder cannot be null");
this.passwordEncoder = encoder;
return this;
}
public UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList(roles.length);
String[] var3 = roles;
int var4 = roles.length;
for(int var5 = 0; var5 < var4; ++var5) {
String role = var3[var5];
Assert.isTrue(!role.startsWith("ROLE_"), () -> {
return role + " cannot start with ROLE_ (it is automatically added)";
});
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return this.authorities((Collection)authorities);
}
public UserBuilder authorities(GrantedAuthority... authorities) {
Assert.notNull(authorities, "authorities cannot be null");
return this.authorities((Collection)Arrays.asList(authorities));
}
public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "authorities cannot be null");
this.authorities = new ArrayList(authorities);
return this;
}
public UserBuilder authorities(String... authorities) {
Assert.notNull(authorities, "authorities cannot be null");
return this.authorities((Collection)AuthorityUtils.createAuthorityList(authorities));
}
public UserBuilder accountExpired(boolean accountExpired) {
this.accountExpired = accountExpired;
return this;
}
public UserBuilder accountLocked(boolean accountLocked) {
this.accountLocked = accountLocked;
return this;
}
public UserBuilder credentialsExpired(boolean credentialsExpired) {
this.credentialsExpired = credentialsExpired;
return this;
}
public UserBuilder disabled(boolean disabled) {
this.disabled = disabled;
return this;
}
public UserDetails build() {
String encodedPassword = (String)this.passwordEncoder.apply(this.password);
return new User(this.username, encodedPassword, !this.disabled, !this.accountExpired, !this.credentialsExpired, !this.accountLocked, this.authorities);
}
}
我知道看见这一坨代码,很多同学肯定很难受,没关系,跟上我的思路就可以。这个UserBuilder是User内置的建造者,最后会通过build方法返回User的实例对象,我们想看的roles方法就在这里。
那么roles方法做了什么呢?中间步骤在干什么我们暂且不看,我们只需要从方法返回了什么看就行。
public UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList(roles.length);
String[] var3 = roles;
int var4 = roles.length;
for(int var5 = 0; var5 < var4; ++var5) {
String role = var3[var5];
Assert.isTrue(!role.startsWith("ROLE_"), () -> {
return role + " cannot start with ROLE_ (it is automatically added)";
});
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return this.authorities((Collection)authorities);
}
roles方法最后返回了一个authorities方法,参数是authorities。。。好吧,我们继续看参数authorities与authorities方法,源码就是绕来绕去的。
authorities参数是roles方法里面新声明的一个List,然后通过for循环,一顿操作猛如虎,把方法的roles参数包装了一下变成SimpleGrantedAuthority对象一个一个添加到了authorities中,这就是authorities参数(中间细节我们不要过于关注,总之就是把参数roles数组的元素一个一个包装成了SimpleGrantedAuthority对象丢进了authorities中)
OK,这下我们知道authorities参数是什么东西了,看看调用的authorities方法吧。
public UserBuilder authorities(Collection<? extends GrantedAuthority> authorities)
Assert.notNull(authorities, "authorities cannot be null");
this.authorities = new ArrayList(authorities);
return this;
}
断言不用看了,这里直接就是让UserBuilder的authorities等于我们的authorities(注意,这里的this不是指User而是指UserBuilder,因为UserBuilder是User的内部类)
我们看完源码之后发现,总而言之,roles方法就是把参数roles包装成了authorities,所以说User没有roles这个字段。而我们额外定义的role字段,实际上就是roles方法要传的参数(好好想想,别被绕晕了)
至于为什么是role而不是roles字段,是因为笔者设计这个GrandUser类只能拥有一个role而不能拥有多个role。
OK,那么如果你已经明白我定义的这几个字段了,我们接着往下看。
public GrandUser(Integer id,String email,String username, String password,String role) {
this(id,email, username, password,toAuth(role));
}
public GrandUser(Integer id,String email,String username, String password,Collection<? extends GrantedAuthority> authorities) {
super(username, password,authorities);
this.id = id;
this.email = email;
this.role = toRole(authorities);
}
这是两个构造方法,第一个构造方法间接调用了第二个构造方法,而第二个构造方法调用了User的构造方法(这就是继承让我觉得最难受的地方,处处受父类制约)。
感觉这构造方法思路也很明确,role会包装成authorities然后调用User的构造方法,然后赋值给GrandUser的role。这里我们并不需要继续关注toRole和toAuth两个方法,因为后续我会根据源码继续讲解。
OK,我们现在来看看GrandUserBuilder的build方法。
public GrandUser build() {
if(role != null)
return new GrandUser(id,email, username, password,role);
else if (authorities != null)
return new GrandUser(id,email, username, password,authorities);
else
return null;
}
这里我们主要对role字段与authorities进行了判断,就是有role调用包装role的构造方法,有authorities直接调用使用authorities的构造方法,都没有就返回null。
到这里我们的代码思路就清晰许多了。我们的GrandUser类到底是怎么一回事呢?我们是通过继承User类实现的拓展功能。所以构造方法都直接或间接调用了User的构造方法。然后我们发现了父类authorities与roles之间的秘密,我们于是也想办法把role字段包装成了authorities,然后build的时候对authorities与roles进行判断,这样就是整个GrandUser类的实现思路。(应该没有被绕晕吧)
那么最后我们看一眼toAuth与toRole这两个方法,也就是roles与authorities之间到底是怎么一回事
先看User类中roles的源码:
public UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList(roles.length);
String[] var3 = roles;
int var4 = roles.length;
for(int var5 = 0; var5 < var4; ++var5) {
String role = var3[var5];
Assert.isTrue(!role.startsWith("ROLE_"), () -> {
return role + " cannot start with ROLE_ (it is automatically added)";
});
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return this.authorities((Collection)authorities);
}
好吧,最后还是要认真看看这个roles到底是怎么一回事。前面我们说了包装,那么我们看看包装是怎么包装的。别看这里一堆var3,var4啥的,你认真看一眼for循环就会发现,还是循环的roles数组,那我们再看看authorities里面添加的是什么,原来就是一个"ROLE_" + role构造的SimpleGrantedAuthority,原来到最后只不过是加个前缀丢到构造函数里面就能转换了,是不是豁然开朗。
这里直接给出toAuth与toRole这两个方法的实现:
public static Collection<? extends GrantedAuthority> toAuth(String role) {
List<GrantedAuthority> authorities = new ArrayList<>();
Assert.isTrue(!role.startsWith("ROLE_"), () -> role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
return new ArrayList<>((Collection)authorities);
}
public String toRole(Collection<? extends GrantedAuthority> authorities) {
if (authorities == null) {
return null;
}
//得到authorities的第一个元素
GrantedAuthority authority = authorities.iterator().next();
if (authority.getAuthority().startsWith("ROLE_")) {
return authority.getAuthority().substring("ROLE_".length());
}
return null;
}
至此,GrandUser类讲解完毕。