学习目标:
Spring Security 应用级别的安全主要包含两个主要部分,即登录认证(Authentication)和访问授权(Authorization),首先用户登录的时候传入登录信息,登录验证器完成登录认证并将登录认证好的信息存储到请求上下文,然后再进行其他操作,如在进行接口访问、方法调用时,权限认证器从上下文中获取登录认证信息,然后根据认证信息获取权限信息,通过权限信息和特定的授权策略决定是否授权。
pring security 的核心功能主要包括:
认证 (你是谁)
授权 (你能干什么)
攻击防护 (防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式
学习内容:
接下来,我们就通过一个具体的案例,来讲解如何进行Spring Security的整合,然后借助Spring Security实现登录认证和访问控制。
生成项目模板
为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站。
1. 打开浏览器,访问:https://start.spring.io/
2. 根据页面提示,选择构建工具,开发语言,项目信息等。
3. 点击 Generate the project,生成项目模板,生成之后会将压缩包下载到本地。
4. 使用IDE导入项目,我这里使用Eclipse,通过导入Maven项目的方式导入。
添加相关依赖
清理掉不需要的测试类及测试依赖,添加 Maven 相关依赖,这里需要添加上web、swagger、spring security、jwt和fastjson的依赖,Swagge和fastjson的添加是为了方便接口测试。
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 http://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.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.louis.springboot</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!-- 打包时拷贝MyBatis的映射文件 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/sqlmap/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
添加相关配置
1.添加swagger 配置
添加一个swagger 配置类,在工程下新建 config 包并添加一个 SwaggerConfig 配置类,除了常规配置外,加了一个令牌属性,可以在接口调用的时候传递令牌。令牌使用JWT token
SwaggerConfig.java
/**
* Copyright (C), 2015-2021, 北京仁科互动网络技术有限公司
* FileName: Swagger2
* Author: Leipengcheng
* Date: 2021/7/12 17:07
* Description:
*/
package com.rkhd.ienterprise.apps.bi.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
* 配置类Swagger2Config
*
* @author Leipengcheng
* @create 2021/7/12
* @since 1.0.0
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*
* @return
*/
@Bean
public Docket createRestApi(){
// 添加请求参数,我们这里把token作为请求头部参数传入后端
ParameterBuilder parameterBuilder = new ParameterBuilder();
List<Parameter> parameters = new ArrayList<Parameter>();
parameterBuilder.name("Authorization").description("令牌").modelRef(new ModelRef("string")).parameterType("header")
.required(false).build();
parameters.add(parameterBuilder.build());
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build().globalOperationParameters(parameters);
}
/**
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
* 访问地址:http://项目实际地址/swagger-ui.html
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//页面标题
.title("BI数据智能API接口文档")
//创建人
.contact(new Contact("雷鹏程", "https://wiki.ingageapp.com/pages/viewpage.action?pageId=64266431","leipc@xiaoshouyi.com"))
//版本号
.version("1.0")
//描述
.description("BI系统API描述")
.build();
}
}
swagger界面如下:
2.添加跨域 配置
package com.rkhd.ienterprise.apps.bi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author liudc
* @date 2021/9/6 9:49
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 允许跨域访问的路径
registry.addMapping("/**")
// 允许跨域访问的源
.allowedOrigins("*")
// 允许请求方法
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
// 预检间隔时间
.maxAge(168000)
// 允许头部设置
.allowedHeaders("*")
// 是否发送cookie
.allowCredentials(true);
}
}
安全配置类
下面这个配置类是Spring Security的关键配置。
在这个配置类中,我们主要做了以下几个配置:
1. 访问路径URL的授权策略,如登录、Swagger访问免登录认证等
2. 指定了登录认证流程过滤器 JwtLoginFilter,由它来触发登录认证(也可以自定义登录认证,我选择自定义认证 后面会有详细解释)
3. 指定了自定义身份认证组件 JwtAuthenticationProvider,并注入 UserDetailsService
4. 指定了访问控制过滤器 JwtAuthenticationFilter,在授权时解析令牌和设置登录状态
5. 指定了退出登录处理器,因为是前后端分离,防止内置的登录处理器在后台进行跳转
WebSecurityConfig.java
需要实现 WebSecurityConfigurerAdapter
package com.rkhd.ienterprise.apps.bi.config;
import com.rkhd.ienterprise.apps.bi.security.JwtAuthenticationFilter;
import com.rkhd.ienterprise.apps.bi.security.JwtAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
/**
* @author liudc
* @date 2021/9/6 9:58
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义登录身份认证组件
auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
http.cors().and().csrf().disable()
.authorizeRequests()
// 跨域预检请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 登录URL
.antMatchers("/login").permitAll()
// swagger
.antMatchers("/swagger**/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/**").permitAll()
.antMatchers("/captcha").permitAll()
.antMatchers("/logout").permitAll()
// 其他所有请求需要身份认证
.anyRequest().authenticated();
// 退出登录处理器
http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
// 开启登录认证流程过滤器
// http.addFilterBefore(new JwtLoginFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
// 访问控制时登录状态检查过滤器
http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
验证码配置类:
package com.rkhd.ienterprise.apps.bi.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* 验证码配置类
* @author liudc
* @date 2021/9/6 10:37
*/
@Configuration
public class CaptchaConfig {
@Bean
public DefaultKaptcha defaultKaptcha(){
//验证码生成器
DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
//配置
Properties properties = new Properties();
//是否有边框
properties.setProperty("kaptcha.border", "yes");
//设置边框颜色
properties.setProperty("kaptcha.border.color", "105,179,90");
//边框粗细度,默认为1
properties.setProperty("kaptcha.border.thickness","1");
//验证码
properties.setProperty("kaptcha.session.key","code");
//验证码文本字符颜色 默认为黑色
properties.setProperty("kaptcha.textproducer.font.color", "blue");
//设置字体样式
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
//字体大小,默认40
properties.setProperty("kaptcha.textproducer.font.size", "30");
//字符长度,默认为5
properties.setProperty("kaptcha.textproducer.char.length", "4");
//字符间距 默认为2
properties.setProperty("kaptcha.textproducer.char.space", "4");
//验证码图片宽度 默认为200
properties.setProperty("kaptcha.image.width", "100");
//验证码图片高度 默认为40
properties.setProperty("kaptcha.image.height", "40");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
package com.rkhd.ienterprise.apps.bi.controller;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* 验证码
* @author liudc
* @date 2021/9/6 10:37
*/
@RestController
public class CaptchaController {
@Autowired
private DefaultKaptcha defaultKaptcha;
@ApiOperation(value = "验证码")
@GetMapping(value = "/captcha",produces = "image/jpeg")
public void captcha(HttpServletRequest request, HttpServletResponse response){
// 定义response输出类型为image/jpeg类型
response.setDateHeader("Expires", 0);
// Set standard HTTP/1.1 no-cache headers.
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Set standard HTTP/1.0 no-cache header.
response.setHeader("Pragma", "no-cache");
// return a jpeg
response.setContentType("image/jpeg");
//-------------------生成验证码 begin --------------------------
//获取验证码文本内容
String text = defaultKaptcha.createText();
System.out.println("验证码内容:"+text);
//将验证码文本内容放入session
request.getSession().setAttribute("captcha",text);
//根据文本验证码内容创建图形验证码
BufferedImage image = defaultKaptcha.createImage(text);
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
//输出流输出图片,格式为jpg
ImageIO.write(image,"jpg",outputStream);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (null!=outputStream){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//-------------------生成验证码 end --------------------------
}
}
登录身份认证组件
上面说到登录认证是通过调用 AuthenticationManager 的 authenticate(token) 方法实现的,而 AuthenticationManager 又是通过调用 AuthenticationProvider 的 authenticate(Authentication authentication) 来完成认证的,所以通过定制 AuthenticationProvider 也可以完成各种自定义的需求,我们这里只是简单的继承 DaoAuthenticationProvider 展示如何自定义,具体的大家可以根据各自的需求按需定制。
package com.rkhd.ienterprise.apps.bi.security;
import com.rkhd.ienterprise.security.MD5Util;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @author liudc
* @date 2021/9/6 10:00
* 登录身份认证组件
*/
public class JwtAuthenticationProvider extends DaoAuthenticationProvider {
public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
setUserDetailsService(userDetailsService);
setPasswordEncoder(new BCryptPasswordEncoder());
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 可以在此处覆写整个登录认证逻辑
return super.authenticate(authentication);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
String password = null;
// 可以在此处覆写密码验证逻辑
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
try {
password = MD5Util.encrypt(presentedPassword);
} catch (Exception e) {
e.printStackTrace();
}
if (!password.equals(userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
// super.additionalAuthenticationChecks(userDetails, authentication);
}
}
登录认证检查过滤器
package com.rkhd.ienterprise.apps.bi.security;
import com.rkhd.ienterprise.apps.bi.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录认证检查过滤器
* @author liudc
* @date 2021/9/6 10:01
*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取token, 并检查登录状态
SecurityUtils.checkAuthentication(request);
chain.doFilter(request, response);
}
}
自定义令牌对象:
package com.rkhd.ienterprise.apps.bi.security;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 自定义令牌对象
* @author liudc
* @date 2021/9/6 10:13
*/
public class JwtAuthenticatioToken extends UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = 1L;
private String token;
public JwtAuthenticatioToken(Object principal, Object credentials){
super(principal, credentials);
}
public JwtAuthenticatioToken(Object principal, Object credentials, String token){
super(principal, credentials);
this.token = token;
}
public JwtAuthenticatioToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, String token) {
super(principal, credentials, authorities);
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
用户登录认证信息查询
package com.rkhd.ienterprise.apps.bi.security;
import com.rkhd.ienterprise.apps.bi.dto.BiUser;
import com.rkhd.ienterprise.apps.bi.exception.ManageException;
import com.rkhd.ienterprise.apps.bi.service.impl.BoardServiceImpl;
import com.rkhd.ienterprise.apps.bi.service.user.UserService;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.rkhd.ienterprise.apps.bi.utils.BiResultEnum.BOARD_SAVE_DB_ERROR_2;
/**
* 用户登录认证信息查询
*
* @author liudc
* @date 2021/9/6 10:05
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger LOG = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
BiUser user = null;
Set<String> roles = null;
try {
user = userService.getUsers(username);
if (user == null) {
throw new UsernameNotFoundException("该用户不存在");
}
} catch (ManageException e) {
LOG.error(ExceptionUtils.getStackTrace(e));
}
// 用户权限列表,根据用户拥有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口对比,决定是否可以调用接口
// Set<String> permissions = biUserService.findPermissions(username);
try {
roles = userService.getRoles(user.getId());
} catch (ManageException e) {
e.printStackTrace();
}
List<GrantedAuthority> grantedAuthorities = roles.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
return new JwtUserDetails(username, user.getPassword(), grantedAuthorities);
}
}
权限封装和安全用户模型 根据角色赋予不同的功能
package com.rkhd.ienterprise.apps.bi.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
/**
* 安全用户模型
* @author liudc
* @date 2021/9/6 10:16
*/
public class JwtUserDetails extends User {
private static final long serialVersionUID = 1L;
public JwtUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
public JwtUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}
package com.rkhd.ienterprise.apps.bi.security;
import org.springframework.security.core.GrantedAuthority;
/**
* 权限封装
* @author liudc
* @date 2021/9/6 10:30
*/
public class GrantedAuthorityImpl implements GrantedAuthority {
private static final long serialVersionUID = 1L;
private String authority;
public GrantedAuthorityImpl(String authority) {
this.authority = authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
@Override
public String getAuthority() {
return this.authority;
}
}
3.1 UserDetailsService
接口。该接口只提供了一个方法:
该方法很容易理解:通过用户名来加载用户。这个方法主要用于从系统数据中查询并加载具体的用户到Spring Security中。
3.2 UserDetails
从上面可以知道最终交给Spring Security的是。该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象中去。默认提供了:
用户的权限集, 默认需要添加前缀
用户的加密后的密码, 不加密会使用前缀
应用内唯一的用户名
账户是否过期
账户是否锁定
凭证是否过期
用户是否可用
如果以上的信息满足不了你使用,你可以自行实现扩展以存储更多的用户信息。比如用户的邮箱、手机号等等。通常我们使用其实现类:
该类内置一个建造器会很方便地帮助我们构建对象,后面我们会用到它。
SpringSecurity-1-UserDetails接口
这个接口是我们自己用来定义用户表的结构的
SpringSecurity自己的用户信息只包含了Username,password,roles,假如我希望用户的实体类中还有性别sex字段,那么就没有办法了,所以SpringSecurity提供了UserDetails接口,我们可以自己新建一个包含sex字段的类,然后该类implements UserDetails接口,就可以获取我们说的这个sex字段
UserDetails实例是通过UserDetailsService接口的loadUserByUsername方法返回的,可以参考这篇文章创建自己的UserDetails(本文中包含sex的那个类)
UserDetails接口中有一个getAuthorities方法,这个方法返回的是权限,但是我们返回的权限必须带有“ROLE_”开头才可以,spring会自己截取ROLE_后边的字符串,也就是说,比如:我的权限叫ADMIN,那么,我返回告诉spring security的时候,必须告诉他权限是ROLE_ADMIN,这样spring security才会认为权限是ADMIN
示例代码:
假设一个用户拥有两个权限,一个叫权限1,一个叫权限2,下面的代码演示了如何使用UserDetails接口获取这两个权限,其本质就是实现getAuthorities()方法
public MyUser implements UserDetails{
// 这个GrantedAuthority是什么呢?其实它就是权限(Role),
// 因为本例中该用户有两个权限,所以这个Collection的长度是2
@Override
public Collection<? extends GrantedAuthority> getAuthorities(){
String role1="ROLE_权限1";
String role2="ROLE_权限2";
GrantedAuthority authority1=new SimpleGrantedAuthority(role1);
GrantedAuthority authority2=new SimpleGrantedAuthority(role2);
List<GrantedAuthority> list=new ArrayList<>();
list.add(authority1);
list.add(authority2);
return list;
}
}
根据UserDetails接口,我们知道spring更推荐将权限用逗号隔开的方式存到数据库user表中,而不推荐使用1对多的方式来存储权限(单独的一个user表和权限表,然后使用一个关系表将两个表关联起来)
参考链接