4-11 Spring Security及SSO
1. 关于用户身份认证与授权
Spring Security是用于解决认证与授权的框架。
在根项目下创建新的csmall-passport
子模块,最基础的依赖项包括spring-boot-starter-web
与spring-boot-starter-security
(为避免默认存在的测试类出错,应该保留测试的依赖项spring-boot-starter-test
),完整的csmall-passwort
的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>cn.tedu</groupId>
<artifactId>csmall-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 当前项目的信息 -->
<groupId>cn.tedu</groupId>
<artifactId>csmall-passport</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 当前项目需要使用的依赖项 -->
<dependencies>
<!-- Spring Boot Web:支持Spring MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Security:处理认证与授权 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Test:测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
调整完成后,即可启动项目,在启动的日志中,可以看到类似以下内容:
Using generated security password: 2abb9119-b5bb-4de9-8584-9f893e4a5a92
Spring Security有默认登录的账号和密码(以上提示的值),密码是随机的,每次启动项目都会不同。
Spring Security默认要求所有的请求都是必须先登录才允许的访问,可以使用默认的用户名user
和自动生成的随机密码来登录。在测试登录时,在浏览器访问当前主机的任意网址都可以(包括不存在的资源),会自动跳转到登录页(是由Spring Security提供的,默认的URL是:http://localhost:8080/login),当登录成功后,会自动跳转到此前访问的URL(跳转登录页之前的URL),另外,还可以通过 http://localhost:8080/logout 退出登录。
Spring Security的依赖项中包括了Bcrypt算法的工具类,Bcrypt是一款非常优秀的密码加密工具,适用于对需要存储下来的密码进行加密处理。
package cn.tedu.csmall.passport;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BcryptPasswordEncoderTests {
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Test
public void testEncode() {
// 原文相同的情况,每次加密得到的密文都不同
for (int i = 0; i < 10; i++) {
String rawPassword = "123456";
String encodedPassword = passwordEncoder.encode(rawPassword);
System.out.println("rawPassword = " + rawPassword);
System.out.println("encodedPassword = " + encodedPassword);
}
// rawPassword = 123456
// encodedPassword = $2a$10$HWuJ9WgPazrwg9.isaae4u7XdP7ohH7LetDwdlTWuPC4ZAvG.Uc7W
// encodedPassword = $2a$10$rOwgZMpDvZ3Kn7CxHWiEbeC6bQMGtfX.VYc9DCzx9BxkWymX6FbrS
// encodedPassword = $2a$10$H8ehVGsZx89lSVHwBVI37OkxWm8LXei4T1o5of82Hwc1rD0Yauhky
// encodedPassword = $2a$10$meBbCiHZBcYn7zMrZ4fPd.hizrsiZhAu8tmDk.P8QJcCzSQGhXSvq
// encodedPassword = $2a$10$bIRyvV29aoeJLo6hh1M.yOvKoOud5kC7AXDMSUW4tF/DlcG0bLj9C
// encodedPassword = $2a$10$eq5BuoAiQ6Uo0.TOPZOFPuRNlPl3t2GoTlaFoYfBu3/Bo3tLzx.v2
// encodedPassword = $2a$10$DhTSwQfNdqrGgHRmILmNLeV0jt3ZXL435xz0fwyZ315ciI5AuI5gi
// encodedPassword = $2a$10$T.8/ISoLOdreEEkp4py36O0ZYfihDbdHDuIElZVF3uEgMOX.8sPcK
// encodedPassword = $2a$10$hI4wweFOGJ7FMduSmcjNBexbKFOjYMWl8hkug0n0k1LNR5vEyhhMW
// encodedPassword = $2a$10$b4ztMI6tWoiJuoDYKwr7DOywsPkkCdvDxbPfmEsLdp11NdABS7wyy
}
@Test
public void testMatches() {
String rawPassword = "123456";
String encodedPassword = "$2a$10$hI4wweFOGJ7FMduSmCjNBexbKFOjYMWl8hkug0n0k1LNR5vEyhhMW";
boolean matchResult = passwordEncoder.matches(rawPassword, encodedPassword);
System.out.println("match result : " + matchResult);
}
}
如果要使得Spring Security能使用数据库中的信息(数据库中的用户名与密码)来验证用户身份(认证),首先,必须实现“根据用户名查询此用户的登录信息(应该包括权限信息)”的查询功能,要实现此查询,需要执行的SQL语句大致是:
select
ams_admin.id,
ams_admin.username,
ams_admin.password,
ams_admin.is_enable,
ams_permission.value
from ams_admin
left join ams_admin_role on ams_admin.id = ams_admin_role.admin_id
left join ams_role_permission on ams_admin_role.role_id = ams_role_permission.role_id
left join ams_permission on ams_role_permission.permission_id = ams_permission.id
where username='root';
要在当前模块(csmall-passport
)中实现此查询功能,需要:
[
csmall-passport
] 添加数据库编程的相关依赖mysql-connector-java
mybatis-spring-boot-starter
durid
/druid-spring-boot-starter
[
csmall-passport
] 添加连接数据库的配置信息[
csmall-passport
] 创建MybatisConfiguration
配置类,用于配置@MapperScan
[
csmall-passport
] 在配置文件中配置mybatis.mapper-locations
属性,以指定XML文件的位置[
csmall-pojo
] 创建AdminLoginVO
类@Data
public class AdminLoginVO implements Serializable {
private Long id;
private String username;
private String password;
private Integer isEnable;
private List<String> permissions;
}
[
csmall-passport
] 在pom.xml
中添加对csmall-pojo
的依赖[
csmall-passport
] 在src/main/java
下的cn.tedu.csmall.passport
包下创建mapper.AdminMapper.java
接口[
csmall-passport
] 在接口中添加抽象方法:AdminLoginVO getLoginInfoByUsername(String username);
在
src/main/resources
下创建mapper
文件夹,并在此文件夹下粘贴得到AdminMapper.xml
在
AdminMapper.xml
中配置以上抽象方法映射的SQL查询:<!-- 忽略固定的代码 --> <mapper namespace="cn.tedu.csmall.passport.mapper.AdminMapper"> <!-- AdminLoginVO getLoginInfoByUsername(String username); -->
<select id="getLoginInfoByUsername" resultMap="LoginInfoResultMap">
select
<include refid="LoginInfoQueryFields" />
from ams_admin
left join ams_admin_role
on ams_admin.id = ams_admin_role.admin_id
left join ams_role_permission
on ams_admin_role.role_id = ams_role_permission.role_id
left join ams_permission
on ams_role_permission.permission_id = ams_permission.id
where username=#{username}
</select> <sql id="LoginInfoQueryFields">
<if test="true">
ams_admin.id,
ams_admin.username,
ams_admin.password,
ams_admin.is_enable,
ams_permission.value
</if>
</sql> <resultMap id="LoginInfoResultMap" type="cn.tedu.csmall.pojo.vo.AdminLoginVO">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
<result column="is_enable" property="isEnable" />
<collection property="permissions" ofType="java.lang.String">
<!-- 以下配置类似在Java中执行 new String("/pms/product/read") -->
<constructor>
<arg column="value" />
</constructor>
</collection>
</resultMap> </mapper>
完成后,还应该编写并执行测试
根据有效的用户名查询出的结果例如:
AdminLoginVO(
id=1,
username=root,
password=1234,
isEnable=1,
permissions=[
/pms/product/read,
/pms/product/update,
/pms/product/delete,
/ams/admin/read,
/ams/admin/update,
/ams/admin/delete
]
)
Spring Security的认证机制中包含:当客户端提交登录后,会自动调用UserDetailsService
接口(Spring Security定义的)的实现类对象中的UserDetails loadUserByUsername(String username)
方法(根据用户名加载用户数据),将得到UserDetails
类型的对象,此对象中应该至少包括此用户名对应的密码、权限等信息,接下来,Spring Security会自动完成密码的对比,并确定此次客户端提交的信息是否允许登录!类似于:
// Spring Security的行为
UserDetails userDetails = userDetailsService.loadUserByUsername("chengheng");
// Spring Security将从userDetails中获取密码,用于验证客户端提交的密码,判断是否匹配
所以,要实现Spring Security通过数据库的数据来验证用户名与密码(而不是采用默认的user
用户名和随机的密码),则在cn.tedu.csmall.passport
包下创建security.UserDetailsServiceImpl
类,实现UserDetailsService
接口,并重写接口中的抽象方法:
package cn.tedu.csmall.passport.security;
import cn.tedu.csmall.passport.mapper.AdminMapper;
import cn.tedu.csmall.pojo.vo.AdminLoginVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
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;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private AdminMapper adminMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("根据用户名查询尝试登录的管理员信息,用户名=" + s);
AdminLoginVO admin = adminMapper.getLoginInfoByUsername(s);
System.out.println("通过持久层进行查询,结果=" + admin);
if (admin == null) {
System.out.println("根据用户名没有查询到有效的管理员数据,将抛出异常");
throw new BadCredentialsException("登录失败,用户名不存在!");
}
System.out.println("查询到匹配的管理员数据,需要将此数据转换为UserDetails并返回");
UserDetails userDetails = User.builder()
.username(admin.getUsername())
.password(admin.getPassword())
.accountExpired(false)
.accountLocked(false)
.disabled(admin.getIsEnable() != 1)
.credentialsExpired(false)
.authorities(admin.getPermissions().toArray(new String[] {}))
.build();
System.out.println("转换得到UserDetails=" + userDetails);
return userDetails;
}
}
完成后,再配置密码加密器即可:
package cn.tedu.csmall.passport.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfiguration {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
重启项目,可以发现在启动过程中不再生成随机的密码值,在浏览器上访问此项目的任何URL,进入登录页,即可使用数据库中的管理员数据进行登录。
在Spring Security,默认使用Session机制存储成功登录的用户信息(因为HTTP协议是无状态协议,并不保存客户端的任何信息,所以,同一个客户端的多次访问,对于服务器而言,等效于多个不同的客户端各访问一次,为了保存用户信息,使得服务器端能够识别客户端的身份,必须采取某种机制),当下,更推荐使用Token或相关技术(例如JWT)来解决识别用户身份的问题。
JWT = JSON Web Token,它是通过JSON格式组织必要的数据,将数据记录在票据(Token)上,并且,结合一定的算法,使得这些数据会被加密,然后在网络上传输,服务器端收到此数据后,会先对此数据进行解密,从而得到票据上记录的数据(JSON数据),从而识别用户的身份,或者处理相关的数据。
其实,在客户端第1次访问服务器端时,是“空着手”访问的,不会携带任何票据数据,当服务器进行响应时,会将JWT响应到客户端,客户端从第2次访问开始,每次都应该携带JWT发起请求,则服务器都会收到请求中的JWT并进行处理。
要使用JWT,需要添加相关的依赖项,可以实现生成JWT、解析JWT的框架较多,目前,主流的JWT框架可以是jjwt
:
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
则在根项目中管理以上依赖,并在csmall-passport
中添加以上依赖。
测试使用JWT:
package cn.tedu.csmall.passport;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtTests {
// 密钥
String secretKey = "fgfdsfadsfadsafdsafdsfadsfadsfdsafdasfdsafdsafdsafds4rttrefds";
@Test
public void testGenerateJwt() {
// Claims
Map<String, Object> claims = new HashMap<>();
claims.put("id", 9527);
claims.put("name", "星星");
// JWT的组成部分:Header(头),Payload(载荷),Signature(签名)
String jwt = Jwts.builder()
// Header:指定算法与当前数据类型
// 格式为: { "alg": 算法, "typ": "jwt" }
.setHeaderParam(Header.CONTENT_TYPE, "HS256")
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
// Payload:通常包含Claims(自定义数据)和过期时间
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000))
// Signature:由算法和密钥(secret key)这2部分组成
.signWith(SignatureAlgorithm.HS256, secretKey)
// 打包生成
.compact();
// eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJuYW1lIjoi5pif5pifIiwiaWQiOjk1MjcsImV4cCI6MTY1NTM2NTY3N30.QwBYVgdkdibEpD-pjX4sKfNu3tw8hBLcJy4-UcN1F3c
// eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJuYW1lIjoi5pif5pifIiwiaWQiOjk1MjcsImV4cCI6MTY1NTM2NzMwMn0.qBBHearv8iHPNjtDGtO2ci_-KAL4CALHnwzaG_ljsQg
System.out.println(jwt);
}
@Test
public void testParseJwt() {
String jwt = "eyJjdHkiOiJIUzI1NiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJuYW1lIjoi5pif5pifIiwiaWQiOjk1MjcsImV4cCI6MTY1NTM2NzMwMn0.qBBHearv8iHPNjtDGtO2ci_-KAL4CALHnwzaG_ljsQg";
Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
Object id = claims.get("id");
Object name = claims.get("name");
System.out.println("id=" + id);
System.out.println("name=" + name);
}
}
当JWT数据过期时,异常信息例如:
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2022-06-16T15:47:57Z. Current time: 2022-06-16T16:08:32Z, a difference of 1235869 milliseconds. Allowed clock skew: 0 milliseconds.
当JWT解析失败(数据有误)时,异常信息例如:
io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"cty"�"HS256","typ":"JWT","alg":"HS256"}
当生成JWT和解析JWT的密钥不一致时,异常信息例如:
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
要在Spring Security中使用JWT,至少需要:
不能让Spring Security按照原有模式来处理登录(原有模式中,登录成功后,自动装用户信息存储到Session中,且跳转页面),需要
需要自动装配
AuthenticationManager
对象- 使得
SecurityConfiguration
配置类继承自WebSecurityConfigurerAdapter
类,重写其中的xx
方法,在此方法中直接调用父级方法即可,并在此方法上添加@Bean
注解
- 使得
创建
AdminLoginDTO
类,此类中应该包含用户登录时需要提交的用户名、密码创建
IAdminService
接口在
IAdminService
接口中添加登录的抽象方法String login(AdminLoginDTO adminLoginDTO);
创建
AdminServiceImpl
类,实现以上接口- 在实现过程中,调用
AuthenticationManager
实现认证,当认证成功后,生成JWT并返回
- 在实现过程中,调用
创建
AdminController
类,在类中处理登录请求在
SecurityConfiguration
中配置Spring Security,对特定的请求进行放行(默认所有请求都必须先登录)
相关代码:SecurityConfiguration
:
package cn.tedu.csmall.passport.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用防跨域攻击
http.csrf().disable();
// URL白名单
String[] urls = {
"/admins/login"
};
// 配置各请求路径的认证与授权
http.authorizeRequests() // 请求需要授权才可以访问
.antMatchers(urls) // 匹配一些路径
.permitAll() // 允许直接访问(不需要经过认证和授权)
.anyRequest() // 匹配除了以上配置的其它请求
.authenticated(); // 都需要认证
}
}
相关代码:AdminLoginDTO
:
package cn.tedu.csmall.pojo.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class AdminLoginDTO implements Serializable {
private String username;
private String password;
}
相关代码:IAdminService
:
package cn.tedu.csmall.passport.service;
import cn.tedu.csmall.pojo.dto.AdminLoginDTO;
public interface IAdminService {
String login(AdminLoginDTO adminLoginDTO);
}
相关代码:AdminServiceImpl
:
package cn.tedu.csmall.passport.service;
import cn.tedu.csmall.pojo.dto.AdminLoginDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
@Service
public class AdminServiceImpl implements IAdminService {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public String login(AdminLoginDTO adminLoginDTO) {
// 准备被认证数据
Authentication authentication
= new UsernamePasswordAuthenticationToken(
adminLoginDTO.getUsername(), adminLoginDTO.getPassword());
// 调用AuthenticationManager验证用户名与密码
// 执行认证,如果此过程没有抛出异常,则表示认证通过,如果认证信息有误,将抛出异常
authenticationManager.authenticate(authentication);
// 如果程序可以执行到此处,则表示登录成功
// 生成此用户数据的JWT
String jwt = "This is a JWT."; // 临时
return jwt;
}
}
相关代码:AdminController
:
package cn.tedu.csmall.passport.controller;
import cn.tedu.csmall.passport.service.IAdminService;
import cn.tedu.csmall.pojo.dto.AdminLoginDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/admins", produces = "application/json; charset=utf-8")
public class AdminController {
@Autowired
private IAdminService adminService;
// http://localhost:8080/admins/login?username=root&password=123456
@RequestMapping("/login")
public String login(AdminLoginDTO adminLoginDTO) {
String jwt = adminService.login(adminLoginDTO);
return jwt;
}
}
以上全部完成后,启动项目,打开浏览器,可以通过 http://localhost:8080/admins/login?username=root&password=123456 这类URL测试登录,使用数据库中的用户名和密码进行尝试。
当通过以上URL进行访问时,其内部过程大概是:
- Spring Security的相关配置会进行URL的检查,来判断是否允许访问此路径
- 所以,需要在
SecurityConfiguration
中将以上路径设置为白名单 - 如果没有将以上路径配置到白名单,将直接跳转到登录页,因为默认所有请求都必须先登录
- 所以,需要在
- 由
AdminController
接收到请求后,调用了IAdminService
接口的实现类对象来处理登录IAdminService
接口的实现是AdminServiceImpl
- 在
AdminServiceImpl
中,调用了AuthenticationManager
处理登录的认证AuthenticationManager
对象调用authenticate()
方法进行登录处理- 内部实现中,会自动调用
UserDetailsService
实现对象的loadUserByUsername()
方法以获取用户信息,并自动完成后续的认证处理(例如验证密码是否正确),所以,在步骤中,具体执行的是UserDetailsServiceImpl
类中重写的方法,此方法返回了用户信息,Spring Security自动验证,如果失败(例如账号已禁用、密码错误等),会抛出异常
- 内部实现中,会自动调用
- 以上调用的
authenticate()
方法如果未抛出异常,可视为认证成功,即登录成功 - 当登录成功时,应该返回此用户的JWT数据(暂时未实现)
4-11 Spring Security及SSO的更多相关文章
- Spring Security OAuth2 SSO 单点登录
基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...
- 【SpringSecurityOAuth2】源码分析@EnableOAuth2Sso在Spring Security OAuth2 SSO单点登录场景下的作用
目录 一.从Spring Security OAuth2官方文档了解@EnableOAuth2Sso作用 二.源码分析@EnableOAuth2Sso作用 @EnableOAuth2Client OA ...
- spring boot:spring security+oauth2+sso+jwt实现单点登录(spring boot 2.3.3)
一,sso的用途 ? 1,如果有多个应用系统,用户只需要登录一次就可以访问所有相互信任的应用系统. 不需要每次输入用户名称和用户密码, 也不需要创建并记忆多套用户名称和用户密码. 2,系统管理员只需维 ...
- Spring Security OAuth2 SSO
通常公司肯定不止一个系统,每个系统都需要进行认证和权限控制,不可能每个每个系统都自己去写,这个时候需要把登录单独提出来 登录和授权是统一的 业务系统该怎么写还怎么写 最近学习了一下Spring Sec ...
- 关于Spring Security中无Session和无状态stateless
Spring Security是J2EE领域使用最广泛的权限框架,支持HTTP BASIC, DIGEST, X509, LDAP, FORM-AUTHENTICATION, OPENID, CAS, ...
- Java安全框架(一)Spring Security
Java安全框架(一)Spring Security 文章主要分三部分 1.Spring Security的架构及核心组件:(1)认证:(2)权限拦截:(3)数据库管理:(4)权限缓存:(5)自定 ...
- spring boot + spring security +前后端分离【跨域】配置 + ajax的json传输数据
1.前言 网上各个社区的博客参差不齐 ,给初学者很大的困扰 , 我琢磨了一天一夜,到各个社区找资料,然后不断测试,遇到各种坑,一言难尽啊,要么源码只有一部分,要么直接报错... 最后实在不行,直接去看 ...
- spring security +MySQL + BCryptPasswordEncoder 单向加密验证 + 权限拦截 --- 心得
1.前言 前面学习了 security的登录与登出 , 但是用户信息 是 application 配置 或内存直接注入进去的 ,不具有实用性,实际上的使用还需要权限管理,有些 访问接口需要某些权限才可 ...
- SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统
一.单点登录SSO介绍 目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...
随机推荐
- 4.18-token验证
在postman编写的每一个叫测试用例,既然收测试用例,那么就会有结果对比 API测试断言tests(判断一个接口测试用例是否成功,或者说是通过,是根据断言的三个条件都成立的情况下得到的结果) 协议状 ...
- 【理论积累】C语言基础理论知识【第一版】
第一个程序 #include <stdio.h> int main(){ printf("Hello World"); } 变量与常量 变量类型:整型[int].字符型 ...
- 120_PowerBI堆积瀑布图_R脚本Visual
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.效果 二.data 三.添加字段 注意红色框标注地方 四.code # 下面用于创建数据帧并删除重复行的代码始终执行, ...
- 无法启动报,To install it, you can run: npm install --save @/components/xxxx.vue
运行的过程中后台报错 npm install --save @/components/xxx.vue 重装了node_modules依然没有用. 其实是组件路径写错了 总结 以后出现提醒安装那个vue ...
- Fast-Rcnn学习笔记
Fast-Rcnn学习笔记 paper code Fast-RCNN总览 step1:图片先放进卷积层 step2:再卷积层的特征图谱上回映射出对应的感兴趣区域 step3:集过一层ROI Pooli ...
- flex布局的总结
1.开启了flex布局的元素叫: flex container 2.里面的直接子元素叫:flex items(默认情况下,所有item都会在一行显示) 3.display属性由flex和inline- ...
- Java_Scanner的使用
目录 Scanner对象 scanner.next()和scanner.nextln()的区别 scanner.hasNext()和scanner.hasNextln() Scanner拓展 视频课程 ...
- electron vue
vue create project vue add vue-cli-plugin-electron-builder node_modules\@vue\cli-service\lib\config\ ...
- NODE.JS exports require理解
node.js exports 的作用是什么? 因为A.js文件想访问B.js文件中的类或函数,是不能直接访问的.为了解决这个问题 node.js 产生了 exports ,exports 实际可以理 ...
- camunda如何调用HTTP REST(Service Task)服务节点
Camunda中的Service Task(服务任务)用于调用服务.在Camunda中,可以通过调用本地Java代码.外部工作项.web服务形式实现的逻辑来完成的. 本文重点描述如何使用web服务 ...