1. 关于用户身份认证与授权

Spring Security是用于解决认证与授权的框架。

在根项目下创建新的csmall-passport子模块,最基础的依赖项包括spring-boot-starter-webspring-boot-starter-security(为避免默认存在的测试类出错,应该保留测试的依赖项spring-boot-starter-test),完整的csmall-passwortpom.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的更多相关文章

  1. Spring Security OAuth2 SSO 单点登录

    基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...

  2. 【SpringSecurityOAuth2】源码分析@EnableOAuth2Sso在Spring Security OAuth2 SSO单点登录场景下的作用

    目录 一.从Spring Security OAuth2官方文档了解@EnableOAuth2Sso作用 二.源码分析@EnableOAuth2Sso作用 @EnableOAuth2Client OA ...

  3. spring boot:spring security+oauth2+sso+jwt实现单点登录(spring boot 2.3.3)

    一,sso的用途 ? 1,如果有多个应用系统,用户只需要登录一次就可以访问所有相互信任的应用系统. 不需要每次输入用户名称和用户密码, 也不需要创建并记忆多套用户名称和用户密码. 2,系统管理员只需维 ...

  4. Spring Security OAuth2 SSO

    通常公司肯定不止一个系统,每个系统都需要进行认证和权限控制,不可能每个每个系统都自己去写,这个时候需要把登录单独提出来 登录和授权是统一的 业务系统该怎么写还怎么写 最近学习了一下Spring Sec ...

  5. 关于Spring Security中无Session和无状态stateless

    Spring Security是J2EE领域使用最广泛的权限框架,支持HTTP BASIC, DIGEST, X509, LDAP, FORM-AUTHENTICATION, OPENID, CAS, ...

  6. Java安全框架(一)Spring Security

    Java安全框架(一)Spring Security ​ 文章主要分三部分 1.Spring Security的架构及核心组件:(1)认证:(2)权限拦截:(3)数据库管理:(4)权限缓存:(5)自定 ...

  7. spring boot + spring security +前后端分离【跨域】配置 + ajax的json传输数据

    1.前言 网上各个社区的博客参差不齐 ,给初学者很大的困扰 , 我琢磨了一天一夜,到各个社区找资料,然后不断测试,遇到各种坑,一言难尽啊,要么源码只有一部分,要么直接报错... 最后实在不行,直接去看 ...

  8. spring security +MySQL + BCryptPasswordEncoder 单向加密验证 + 权限拦截 --- 心得

    1.前言 前面学习了 security的登录与登出 , 但是用户信息 是 application 配置 或内存直接注入进去的 ,不具有实用性,实际上的使用还需要权限管理,有些 访问接口需要某些权限才可 ...

  9. SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统

    一.单点登录SSO介绍   目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...

随机推荐

  1. 论文解读(Debiased)《Debiased Contrastive Learning》

    论文信息 论文标题:Debiased Contrastive Learning论文作者:Ching-Yao Chuang, Joshua Robinson, Lin Yen-Chen, Antonio ...

  2. 借助ADB冻结与卸载Android系统应用(免ROOT)

    背景: 我妈的手机饱受系统应用广告推送之苦,每天都能在通知栏里收到好几条广告.为了给她个清净,本篇博文应运而生. 目标: 卸载安卓系统应用 所用工具: 硬件:我妈的手机(魅蓝5) PC端:Minima ...

  3. vue3 vite 系统标题 系统名称统一配置

    想要统一配置系统名称 或者其他的,需要在vue3中使用 vite 的环境变量 vite 的环境变量 需要创建两个文件(和 vite.config.js 文件同一目录) .env.development ...

  4. Node.js 中的进程和线程

    线程和进程是计算机操作系统的基础概念,在程序员中属于高频词汇,那如何理解呢?Node.js 中的进程和线程又是怎样的呢? 一.进程和线程 1.1.专业性文字定义 进程(Process),进程是计算机中 ...

  5. SpringBoot从0到0.7——第四天

    SpringBoot从0到0.7--第四天 今天进行实战开发一个小项目,SpringBoot和Thymeleaf集成的小项目 因为懒得写写前端,直接找的别人的项目在它的的基础上进行配置,进行修改.gi ...

  6. Spring 源码(14)Spring Bean 的创建过程(5)

    到目前为止,我们知道Spring创建Bean对象有5中方法,分别是: 使用FactoryBean的getObject方法创建 使用BeanPostProcessor的子接口InstantiationA ...

  7. 自学c语言

    C 语言是一种通用的.面向过程式的计算机程序设计语言. 当前最新的 C 语言标准为 C18 前期准备 C 编译器  写在源文件中的源代码是人类可读的源.它需要"编译",转为机器语言 ...

  8. 微信小程序避坑指南——input框里的图标在部分安卓机里无法点击的问题

    问题场景: 下图中的显隐密码和验证码均为包裹在 input标签 中的 image标签, 但在开发测试中发现点击不了这俩个image标签,因为是被input标签的padding挡住了. 解决方法:将im ...

  9. 浅谈BSGS和EXBSGS

    我的 BSGS 和各位犇犇的差不多,但是不需要求逆元 Luogu [ TJOI2007 ] 可爱的质数 原题展现 题目描述 给定一个质数 \(p\),以及一个整数 \(b\),一个整数 \(n\),现 ...

  10. [学习笔记] pd_ds黑科技

    https://www.cnblogs.com/jiqimin/p/11226809.html 丢个链接,跑路 // Author: wlzhouzhuan #pragma GCC optimize( ...