课程链接:SpringSecurity框架教程
开始时间:2022-07-17

快速入门

搭建一个Spring Boot项目

添加基础依赖和创建启动类和controller
controller

@RestController
public class HelloController {@RequestMapping("/hello")public String hello() {return "hello";}
}

启动类

package com.bupt.security_test;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SecurityTestApplication {public static void main(String[] args) {SpringApplication.run(SecurityTestApplication.class, args);}}

此时访问页面localhost:8080/hello
可以看到显示hello这一个单词

引入Security的依赖

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

此时我们再去访问
localhost:8080/hello
会自动跳转到security带的登录界面

登录名默认是user
密码会在控制台打印给你

如果输入错误的账户密码

输入正确

默认有一个退出的接口logout

认证

登录校验流程图

Spring Security流程

本质是一个过滤器链

  • UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

  • ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。 发现异常并重定向

  • FilterSecurityInterceptor:负责权限校验的过滤器。(他实现了过滤器接口)

没想到还有重温之前学拦截器的部分,参考博客

这里只是选了三个典型的,还有其他过滤器,暂不研究
我们可以查看有哪些过滤器

认证流程详解

区分一下概念

  • Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

  • AuthenticationManager接口:定义了认证Authentication的方法

  • UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

  • UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

  • 第一步:提交用户名和密码 传入到UsernamePasswordAuthenticationFilter,这个过滤器把用户名和密码封装为一个Authentication对象,此时还没有权限信息

  • 第二步 调用authenticate方法进行认证,链条走到AuthenticationManager,但他也没完,继续调用DaoAuthenticationProvider的authenticate方法进行认证

  • 第三步 即使已经经过了三个过滤器,还是没有认证,需要继续调用loadUserByUsername方法查询用户,走到第四个链条处了

  • 第四步 根据用户名去查询用户及该用户对应的权限信息,InMemoryUserDetailsManager是在内存中查找的,并把对应用户信息添加上权限信息封装成UserDetails对象进行返回

  • 第五步 UserDetails返回到Provider处,判断PasswordEncoder对比UserDetails中的密码和Authentication的密码是否正确,如果正确就把UserDetails中的权限信息设置到Authentication对象中

  • 第六步 返回Authentication对象到第一个过滤器处,如果成功返回,就使用SecurityContextHolder.getContext().setAuthentication方法村吃该对象,其他过滤器可以通过SecurityContextHolder来获取当前用户信息

我们想想,在第四步中,查询的信息是在内存中查的,而我们需要从数据库查,那就得自己来实现这个接口


之后再请求

那么我们经过JWT拿到UserID后,怎么获取完整信息呢?再去数据库一条条查?会增大IO开销
因此,Redis闪亮登场。
那JWT要去Redis里面查,那总得Redis里面有东西吧,什么时候放呢?就在我们登录接口那里,如果认证通过,生成一个JWT的同时,再把UserID:用户信息存入Redis里面即可

解决问题

思路分析

  • 登录
    ①自定义登录接口
    调用ProviderManager的方法进行认证 如果认证通过生成jwt
    把用户信息存入redis中
    ②自定义UserDetailsService
    在这个实现类中去查询数据库

  • 校验:
    ①定义Jwt认证过滤器
    获取token
    解析token获取其中的userid
    从redis中获取用户信息
    存入SecurityContextHolder

准备工作,添加所需要的Maven依赖以及相应的工具类

功能实现

数据库校验用户

我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。
建表并引入MybatisPuls和mysql驱动的依赖

CREATE TABLE `sys_user` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',`nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称',`password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密码',`status` CHAR(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',`email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱',`phonenumber` VARCHAR(32) DEFAULT NULL COMMENT '手机号',`sex` CHAR(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',`avatar` VARCHAR(128) DEFAULT NULL COMMENT '头像',`user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)',`create_by` BIGINT(20) DEFAULT NULL COMMENT '创建人的用户id',`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',`update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人',`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',`del_flag` INT(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'

配置数据库信息
配置mapper

package com.bupt.security_test.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bupt.security_test.domain.User;public interface UserMapper extends BaseMapper<User> {}

在主启动类上扫描mapper

@MapperScan("com.bupt.security_test.mapper")
public class SecurityTestApplication

测试一下

@SpringBootTest
public class SecurityTestApplicationTests {@Autowiredprivate UserMapper userMapper;@Testpublic void testUserMapper() {List<User> users = userMapper.selectList(null);System.out.println(users);}
}

读取到了数据库数据,说明MyBatisPlus没问题

那我们就要尝试链接自己的数据库的账号密码了
创建一个类实现UserDetailsService接口,重写其中的方法。
这里的==@service注解不能忘记==

package com.bupt.security_test.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bupt.security_test.domain.LoginUser;
import com.bupt.security_test.domain.User;
import com.bupt.security_test.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.Objects;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserName, username);User user = userMapper.selectOne(queryWrapper);//没查到用户就抛异常if (Objects.isNull(user)) {throw new RuntimeException("用户不存在");}//TODO 查询对应权限return new LoginUser(user);}
}

返回的实体类LoginUser我们也定义一下,因为我们的User本身和UserService没关系,需要借助LoginUser包装一下
注意后面几个方法虽然没实现,但返回改为了true而不是默认的false

package com.bupt.security_test.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {private User user;//获取访问权限信息@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}//是否没过期@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}//@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

测试要求输入账号密码

现阶段要把password密码明文前面加上{noop}
不然识别不出来
这个括号里面是说明该密码采取什么方式进行的加密,{noop}表示没有加密
为什么呢,我们再看看上面的图

实际项目中我们不会把密码明文存储在数据库中。
​ 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。

我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。
我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。
我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。

配置一下SecurityConfig

package com.bupt.security_test.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}

写一个测试类看看

    @Testpublic void TestBCryptPasswordEncoder() {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode1 = bCryptPasswordEncoder.encode("1234");String encode2 = bCryptPasswordEncoder.encode("1234");System.out.println(encode1);System.out.println(encode2);}

我们打印输出,会发现每次encode的结果都不一致

$2a$10$NMAeABItflg2UyrWNuOwvu0hFhEqGIk.zyx0RUJbeAm6jrjQq54oi
$2a$10$p5J1O.5THmLqv9ATSv/juep3QxhsUOyGTh/5bL51.8kcwKMNNYlle

我们存数据库的密码不是明文,而是encode后的结果
但不管怎样,用谁加密,对应匹配他还是认识
再看一看

    @Testpublic void TestBCryptPasswordEncoder() {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode1 = bCryptPasswordEncoder.encode("1234");String encode2 = bCryptPasswordEncoder.encode("1234");System.out.println("encode1输出" + encode1);System.out.println("encode1判断是否能匹配rawPassword" + bCryptPasswordEncoder.matches("1234", encode1));System.out.println("encode2输出" + encode2);System.out.println("encode2判断是否能匹配rawPassword" + bCryptPasswordEncoder.matches("1234", encode2));}

输出

encode1输出$2a$10$PJNq2HpUoHdj52zp3dPdNO9wKTpuGJHYeF.nX.NyDjt8jWUBqRM46
encode1判断是否能匹配rawPasswordtrue
encode2输出$2a$10$WAwFkHZ8xWoR3e5D9zkWgefxIrrRsZv8B093x9FYA7I/jCqMw8mvi
encode2判断是否能匹配rawPasswordtrue

这里为什么不同的编码都能匹配上呢,知识超纲了,这好像是什么 盐 值

配置好之后,我们直接输入用户密码就不行了,要更新数据库的密码为加密后的密码才行

JWT工具类

package com.bupt.security_test.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;/*** JWT工具类*/
public class JwtUtil {//有效期为public static final Long JWT_TTL = 60 * 60 * 1000L;// 60 * 60 *1000  一个小时//设置秘钥明文public static final String JWT_KEY = "bupt";public static String getUUID() {String token = UUID.randomUUID().toString().replaceAll("-", "");return token;}/*** 生成jtw** @param subject token中要存放的数据(json格式)* @return*/public static String createJWT(String subject) {JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间return builder.compact();}/*** 生成jwt** @param subject   token中要存放的数据(json格式)* @param ttlMillis token超时时间* @return*/public static String createJWT(String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间return builder.compact();}private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;SecretKey secretKey = generalKey();long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if (ttlMillis == null) {ttlMillis = JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);return Jwts.builder().setId(uuid)              //唯一的ID.setSubject(subject)   // 主题  可以是JSON数据.setIssuer("jdh")     // 签发者.setIssuedAt(now)      // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);}/*** 创建token** @param id* @param subject* @param ttlMillis* @return*/public static String createJWT(String id, String subject, Long ttlMillis) {JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间return builder.compact();}public static void main(String[] args) throws Exception {String jwt = createJWT("2123");System.out.println(jwt);//base64解码Claims claims = parseJWT(jwt);System.out.println(claims.getSubject());//String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";//Claims claims = parseJWT(token);//System.out.println(claims);}/*** 生成加密后的秘钥 secretKey** @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}/*** 解析** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}

执行主方法得到

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJiMGQxYmNjODI0NzI0ODljYjZlMDdiMGIxNDkyMzhkZiIsInN1YiI6IjIxMjMiLCJpc3MiOiJqZGgiLCJpYXQiOjE2NTgwNDM0ODQsImV4cCI6MTY1ODA0NzA4NH0.R4WV-QID5r2gACp7qXYV_fXy13VFAV0cjBzUfgFO-v8
2123

登录接口

自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt

自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。
在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。
认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。

首先说如何放行
SecurityConfig重写方法

 //用来放行,总不能登录界面都要让你认证吧@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();}

然后正常的controller service serviceimpl走一遍

package com.bupt.security_test.controller;import com.bupt.security_test.domain.ResponseResult;
import com.bupt.security_test.domain.User;
import com.bupt.security_test.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class LoginController {@Autowiredprivate LoginService loginService;@PostMapping("/user/login")//RequestBody用来拿JSON传过来的用户名和密码public ResponseResult login(@RequestBody User user) {//登录return loginService.login(user);}
}
public interface LoginService {ResponseResult login(User user);
}
@Service
public class LoginServiceImpl implements LoginService {//这个类在SecurityConfig里面实现的@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisCache redisCache;@Overridepublic ResponseResult login(User user) {//AuthenticationManager authenticate进行用户认证//用户名和密码先封装,而Authentication是接口,我们需要找一个他的实现类//在接口名上 ctrl+alt+鼠标左键,可以看其常用实现类UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());//通过authenticationManager来实现认证,会调用UserDetailsServiceImplAuthentication authenticate = authenticationManager.authenticate(authenticationToken);//如果认证没通过,给出对应的提示if (Objects.isNull(authenticate)) {throw new RuntimeException("登录失败");}//如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回LoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userid = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userid);Map<String, String> map = new HashMap<>();map.put("token", jwt);//把完整的用户信息存入redis  userid作为keyredisCache.setCacheObject("login:" + userid, loginUser);return new ResponseResult(200, "登录成功", map);}
}

而我们需要先暴露AuthenticationManager
因此要在SecurityConfig添加

//重写方法,用来暴露AuthenticationManager@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}

使用postman进行debug

debug看,

//通过authenticationManager来实现认证,会调用UserDetailsServiceImplAuthentication authenticate = authenticationManager.authenticate(authenticationToken);

拿到的 authenticate包含的principal下有user信息

集成redis后,我们再来看看测试结果

我们把这个token拿回工具类解析

Claims claim = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwNWNkN2E1ZDhlYzg0Y2NiYWU5NmM3ZDliMjUyOWViOSIsInN1YiI6IjEiLCJpc3MiOiJqZGgiLCJpYXQiOjE2NTgwNDgyMjcsImV4cCI6MTY1ODA1MTgyN30.8yp6VBEHL2aAB5dTlsG13lsRg_XNlNmVNZ88zumhT4c");System.out.println(claim.getSubject());

得到的输出为1,即该用户的id为1

认证过滤器

校验:

①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder

过滤器代码

package com.bupt.security_test.filter;import com.bupt.security_test.domain.LoginUser;
import com.bupt.security_test.utils.JwtUtil;
import com.bupt.security_test.utils.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;//认证过滤器,那这个过滤器需要在SecurityConfig里面配置他出现的位置
//他要出现在UsernamePasswordAuthenticationFilter之前
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取token,从请求头里面拿String token = request.getHeader("token");//没有token,直接放行//放行后会去执行后面的过滤器//走完后面的过滤器,响应回来的时候还会走一遍过滤器链//如果没有return,回来的时候还会执行一次,就会报错,因为根本没有token//登录接口也会从这里过,但因为没有token就被放行了if (!StringUtils.hasText(token)) {filterChain.doFilter(request, response);return;}String userid;//解析tokentry {Claims claims = JwtUtil.parseJWT(token);userid = claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new RuntimeException("token非法");}//从redis里面获取用户信息String redisKey = "login:" + userid;LoginUser loginUser = redisCache.getCacheObject(redisKey);//存入SecurityContextHolderif (Objects.isNull(loginUser)) {throw new RuntimeException("用户未登录");}//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}

配置过滤器位置
SecurityConfig

//添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

测试一下,发送post请求

debug发现在

if (!StringUtils.hasText(token)) {filterChain.doFilter(request, response);return;}

直接就放行了
然后一直走,完成登录后就返回token
此时我们再来测试一下hello接口

被拦截下来了,需要补充token信息

这样就能访问了

退出登录

清空redis SecurityContextHolder

  //退出登录@RequestMapping("/user/logout")public ResponseResult logout() {return loginService.logout();}

实现类

    //退出登录@Overridepublic ResponseResult logout() {//获取SecurityContextHolder中的用户idUsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();Long userid = loginUser.getUser().getId();//用户如果是未登录状态发起退出请求会被拦截下来,根本到不了这个方法//删除redis中的值redisCache.deleteObject("login:" + userid);return new ResponseResult(200, "注销成功");}


此时我们再使用原来的token访问就不行
结束时间:2022-07-17

Spring Security入门01-22 登录验证功能相关推荐

  1. 【Spring Security入门】06-QQ登录实现

    准备工作 1.在 QQ互联 申请成为开发者,并创建应用,得到APP ID 和 APP Key. 2.了解QQ登录时的 网站应用接入流程.(必须看完看懂) 为了方便各位测试,直接把我自己申请的贡献出来: ...

  2. Spring Security 入门(3-11)Spring Security 的使用-自定义登录验证和回调地址

    配置文件 security-ns.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmln ...

  3. Spring Security入门到实践(一)HTTP Basic在Spring Security中的应用原理浅析

    一.Spring Security简介 打开Spring Security的官网,从其首页的预览上就可以看见如下文字: Spring Security is a powerful and highly ...

  4. Spring Security入门基础

    Spring Security入门基础 文章目录 Spring Security入门基础 一,Spring Security的使用 1.1 基本术语 1.2 基本使用 1.2.1 引入依赖 1.2.2 ...

  5. 5.Spring Security 短信验证码登录

    Spring Security 短信验证码登录 在 Spring Security 添加图形验证码一节中,我们已经实现了基于 Spring Boot + Spring Security 的账号密码登录 ...

  6. Spring Security 入门(五):在 Spring-Boot中的应用

    前言 本文作为入门级的DEMO,完全按照官网实例演示: 项目目录结构 Maven 依赖  <parent>    <groupId>org.springframework.bo ...

  7. Spring Security 入门(四):自定义-Filter

    前文导读 - Spring Security入门(一):登录与退出 - Spring Security入门(二):基于数据库验证 - Spring Security入门(三):密码加密 本文解决问题 ...

  8. Spring Security入门(三):密码加密

    前文导读 - Spring Security入门(一):登录与退出 - Spring Security入门(二):基于数据库验证 Github 地址 https://github.com/ChinaS ...

  9. spring 3.x 学习笔记_spring mvc、spring jdbc 实现网站的登录注册功能

    使用spring mvc.spring jdbc 实现网站的登录注册功能 1.        据业务模型 创建model 一般实现序列化 2.        用spring 注解(@Repositor ...

  10. 关于Spring Security框架 关于单点登录sso

    1.Spring Security的作用 Spring Security主要解决了认证和授权相关的问题. 认证(Authenticate):验证用户身份,即登录. 授权(Authorize):允许用户 ...

最新文章

  1. python处理excel字典-使用Python代码处理Excel
  2. Android中使用ViewStub提高布局性能
  3. Visual Studio 2019没有Setup安装项目(Microsoft Visual Studio Installer Projects)的官方解决方案
  4. 转:论文写作与投稿的一点经验,加两封催稿信
  5. Apache2 httpd.conf配置文件中文版详解
  6. GitHub控件之BadgeView(数字提醒)
  7. 看图说cnblogs-强大的SEO功能【有实例】
  8. MYSQL支持事务吗?
  9. 【评分】个人作业——软件工程实践总结作业
  10. 羊皮卷的故事-第十七章-羊皮卷之十
  11. PetaLinux 添加启动后自动执行脚本
  12. Google SketchUp,一款很有趣的软件,可以创建你所想要的任何3D模型
  13. wordpress主题_20个美丽的Flat WordPress主题,为您的网站增光添彩
  14. JS---------------网页版的消灭星星
  15. java jtextarea 事件_JTextArea的事件处理2
  16. U盘文件被病毒破坏的常见迹象和数据恢复方法
  17. Java爬虫html解析-Jsoup(绿盟极光报告)
  18. 怎样去学习——思维导图
  19. 建立时间和保持时间的计算
  20. 模拟MIC和数字MIC

热门文章

  1. 30分钟学会js新特性
  2. Web身份验证(WebAuthn)
  3. Linux——vi/vim文本编辑器、用户管理、关机重启的相关命令
  4. es6字符串添加html标签,JavaScript_详解JavaScript ES6中的模板字符串,在 ES6 中引入了一种新的字符 - phpStudy...
  5. XJTUSE专业课与实验指南(已经开源)
  6. js简易版歌单播放,可切换下一首
  7. arcengine双击属性表内的一行,定位到该要素,并添加到选择集
  8. 四十七、Fluent近壁面处理
  9. 非线性优化汇总——Matlab优化工具箱(持续更新中)
  10. SI522A/SI523带低功耗自动寻卡功能的13.56MHz非接触式读写器芯片