本文目录

  • 前言
  • 1 自定义AuthenticationToken类
  • 2 自定义AuthenticationProvider类
  • 3 自定义MobilePhoneAuthenticationFilter
  • 3 修改UserService类
  • 5 修改短信服务sendLoginVeryCodeMessage方法
  • 6 修改WebSecurityConfig配置类
  • 7 验证效果

前言

在上一篇文章一文理清SpringSecurity中基于用于名密码的登录认证流程中笔者有详细地介绍了Spring Security登录认证的流程,也为我们在工作中面需要实现自定义的登录认证如手机号+短信验证码、邮箱地址+邮箱验证码以及第三方登录认证等方式的扩展做好了准备。那么本文,笔者就手把手带大家实现在集成了Spring SecuritySpringBoot项目中如何增加一种手机号+短信验证码的方式实现登录认证。

最新为了节约搭建项目的时间成本,本文功能的实现在笔者之前改造过的开源项目 blogserver的基础上进行,项目代码地址笔者会在文末提供,希望读者们都能花个5分钟左右坚持看到文末。

1 自定义AuthenticationToken类

我们自定义的MobilePhoneAuthenticationToken类继承自AbstractAuthenticationToken类,主要提供一个带参构造方法并重写getCredentialsgetPrincipalsetAuthenticatederaseCredential和getName`等方法

public class MobilePhoneAuthenticationToken extends AbstractAuthenticationToken {// 登录身份,这里是手机号private Object principal;// 登录凭证,这里是短信验证码private Object credentials;/*** 构造方法* @param authorities 权限集合* @param principal 登录身份* @param credentials 登录凭据*/public MobilePhoneAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal, Object credentials) {super(authorities);this.principal = principal;this.credentials = credentials;super.setAuthenticated(true);}@Overridepublic Object getCredentials() {return credentials;}@Overridepublic Object getPrincipal() {return principal;}// 不允许通过set方法设置认证标识@Overridepublic void setAuthenticated(boolean authenticated) {if (authenticated) {throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");}super.setAuthenticated(false);}// 擦除登录凭据@Overridepublic void eraseCredentials() {super.eraseCredentials();credentials = null;}// 获取认证token的名字@Overridepublic String getName() {return "mobilePhoneAuthenticationToken";}
}

2 自定义AuthenticationProvider类

我们自定义的MobilePhoneAuthenticationProvider类的时候 我们参照了AbstractUserDetailsAuthenticationProvider类的源码, 同时实现了AuthenticationProviderInitializingBeanMessageSourceAware等三个接口

同时为了实现手机号+短信验证码登录认证的功能,我们在这个类中添加了UserServiceRedisTemplate两个类属性,作为MobilePhoneAuthenticationProvider类的两个构造参数

该类的编码完成后的源码如下:

public class MobilePhoneAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {private UserService userService;private RedisTemplate redisTemplate;private boolean forcePrincipalAsString = false;private static final Logger logger = LoggerFactory.getLogger(MobilePhoneAuthenticationProvider.class);protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();public MobilePhoneAuthenticationProvider(UserService userService, RedisTemplate redisTemplate) {this.userService = userService;this.redisTemplate = redisTemplate;}/*** 认证方法* @param authentication 认证token* @return successAuthenticationToken* @throws AuthenticationException 认证异常*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 首先判断authentication参数必须是一个MobilePhoneAuthenticationToken类型对象Assert.isInstanceOf(MobilePhoneAuthenticationToken.class, authentication,()-> this.messages.getMessage("MobilePhoneAuthenticationProvider.onlySupports", "Only MobilePhoneAuthenticationToken is supported"));// 获取authentication参数的principal属性作为手机号String phoneNo = authentication.getPrincipal().toString();if (StringUtils.isEmpty(phoneNo)) {logger.error("phoneNo cannot be null");throw new BadCredentialsException("phoneNo cannot be null");}// 获取authentication参数的credentials属性作为短信验证码String phoneCode = authentication.getCredentials().toString();if (StringUtils.isEmpty(phoneCode)) {logger.error("phoneCode cannot be null");throw new BadCredentialsException("phoneCode cannot be null");}try {// 调用userService服务根据手机号查询用户信息CustomUser user = (CustomUser) userService.loadUserByPhoneNum(Long.parseLong(phoneNo));// 校验用户账号是否过期、是否被锁住、是否有效等属性userDetailsChecker.check(user);// 根据手机号组成的key值去redis缓存中查询发送短信验证码时存储的验证码String storedPhoneCode = (String) redisTemplate.opsForValue().get("loginVerifyCode:"+phoneNo);if (storedPhoneCode==null) {logger.error("phoneCode is expired");throw new BadCredentialsException("phoneCode is expired");}// 用户登录携带的短信验证码与redis中根据手机号查询出来的登录认证短信验证码不一致则抛出验证码错误异常if (!phoneCode.equals(storedPhoneCode)) {logger.error("the phoneCode is not correct");throw new BadCredentialsException("the phoneCode is not correct");}// 把完成的用户信息赋值给组成返回认证token中的principal属性值Object principalToReturn = user;// 如果强制把用户信息转成字符串,则只返回用户的手机号码if(isForcePrincipalAsString()) {principalToReturn = user.getPhoneNum();}// 认证成功则返回一个MobilePhoneAuthenticationToken实例对象,principal属性为较为完整的用户信息MobilePhoneAuthenticationToken successAuthenticationToken = new MobilePhoneAuthenticationToken(user.getAuthorities(), principalToReturn, phoneCode);return successAuthenticationToken;} catch (UsernameNotFoundException e) {// 用户手机号不存在,如果用户已注册提示用户先去个人信息页面添加手机号码信息,否则提示用户使用手机号注册成为用户后再登录logger.error("user " + phoneNo + "not found, if you have been register as a user, please goto the page of edit user information to  add you phone number, " +"else you must register as a user use you phone number");throw new BadCredentialsException("user " + phoneNo + "not found, if you have been register as a user, please goto the page of edit user information to  add you phone number, " +"else you must register as a user use you phone number");} catch (NumberFormatException e) {logger.error("invalid phoneNo, due it is not a number");throw new BadCredentialsException("invalid phoneNo, due do phoneNo is not a number");}}/*** 只支持自定义的MobilePhoneAuthenticationToken类的认证*/@Overridepublic boolean supports(Class<?> aClass) {return aClass.isAssignableFrom(MobilePhoneAuthenticationToken.class);}@Overridepublic void afterPropertiesSet() throws Exception {Assert.notNull(this.messages, "A message source must be set");Assert.notNull(this.redisTemplate, "A RedisTemplate must be set");Assert.notNull(this.userService, "A UserDetailsService must be set");}@Overridepublic void setMessageSource(MessageSource messageSource) {this.messages = new MessageSourceAccessor(messageSource);}public void setForcePrincipalAsString(boolean forcePrincipalAsString) {this.forcePrincipalAsString = forcePrincipalAsString;}public boolean isForcePrincipalAsString() {return forcePrincipalAsString;}
}

在这个自定义的认证器类中主要在authenticate方法中完成自定义的认证逻辑,最后认证成功之后返回一个新的

MobilePhoneAuthenticationToken对象,principal属性为认证通过后的用户详细信息。

3 自定义MobilePhoneAuthenticationFilter

该类我们参照UsernamePasswordAuthenticationFilter类的源码实现一个专门用于手机号+验证码登录认证的认证过滤器,它的源码如下,我们主要在attemptAuthentication方法中完成从HttpServletRequest类型请求参数中提取手机号和短信验证码等请求参数。然后组装成一个MobilePhoneAuthenticationToken对象,用于调用this.getAuthenticationManager().authenticate方法时作为参数传入。

实现重写attemptAuthentication方法后的MobilePhoneAuthenticationFilter类的源码如下:

/*** 自定义手机登录认证过滤器*/
public class MobilePhoneAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String SPRING_SECURITY_PHONE_NO_KEY = "phoneNo";public static final String SPRING_SECURITY_PHONE_CODE_KEY = "phoneCode";private String phoneNoParameter = SPRING_SECURITY_PHONE_NO_KEY;private String phoneCodeParameter = SPRING_SECURITY_PHONE_CODE_KEY;private boolean postOnly = true;public MobilePhoneAuthenticationFilter(String defaultFilterProcessesUrl) {super(defaultFilterProcessesUrl);}public MobilePhoneAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {super(requiresAuthenticationRequestMatcher);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {if (postOnly && !httpServletRequest.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + httpServletRequest.getMethod());}String phoneNo = obtainPhoneNo(httpServletRequest);if (phoneNo==null) {phoneNo = "";} else {phoneNo = phoneNo.trim();}String phoneCode = obtainPhoneCode(httpServletRequest);if (phoneCode==null) {phoneCode = "";} else {phoneCode = phoneCode.trim();}MobilePhoneAuthenticationToken authRequest = new MobilePhoneAuthenticationToken(new ArrayList<>(), phoneNo, phoneCode);this.setDetails(httpServletRequest, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}@Nullableprotected String obtainPhoneNo(HttpServletRequest request) {return request.getParameter(phoneNoParameter);}@Nullableprotected String obtainPhoneCode(HttpServletRequest request) {return request.getParameter(phoneCodeParameter);}protected void setDetails(HttpServletRequest request, MobilePhoneAuthenticationToken authRequest) {authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}
}

3 修改UserService类

UserService类主要在用来查询用户自定义信息,我们在该类中添加根据手机号查询用户信息方法。注意如果用户表中没有手机号码字段,需要给表新增一个存储手机号码的字段,列类型为bigint, 实体类中该字段为Long类型

UserService类中实现根据用户手机号查询用户信息的实现代码如下:

@Service
@Transactional
public class UserService implements CustomUserDetailsService {@ResourceUserMapper userMapper;@ResourceRolesMapper rolesMapper;@ResourcePasswordEncoder passwordEncoder;private static final Logger logger = LoggerFactory.getLogger(UserService.class);/*** 根据用户手机号查询用户详细信息* @param phoneNum 手机号* @return customUser* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByPhoneNum(Long phoneNum) throws UsernameNotFoundException {logger.info("用户登录认证, phoneNum={}", phoneNum);UserDTO userDTO = userMapper.loadUserByPhoneNum(phoneNum);if (userDTO == null) {// 抛UsernameNotFoundException异常throw  new UsernameNotFoundException("user " + phoneNum + " not exist!");}CustomUser customUser = convertUserDTO2CustomUser(userDTO);return customUser;}/*** UserDTO转CustomUser对象* @param userDTO* @return user*/private CustomUser convertUserDTO2CustomUser(UserDTO userDTO) {//查询用户的角色信息,并返回存入user中List<Role> roles = rolesMapper.getRolesByUid(userDTO.getId());// 权限大的角色排在前面roles.sort(Comparator.comparing(Role::getId));CustomUser user = new CustomUser(userDTO.getUsername(), userDTO.getPassword(),userDTO.getEnabled()==1, true, true,true, new ArrayList<>());user.setId(userDTO.getId());user.setNickname(userDTO.getNickname());user.setPhoneNum(userDTO.getPhoneNum());user.setEmail(userDTO.getEmail());user.setUserface(userDTO.getUserface());user.setRegTime(userDTO.getRegTime());user.setUpdateTime(userDTO.getUpdateTime());user.setRoles(roles);user.setCurrentRole(roles.get(0));return user;}}

UserDTOCustomUser两个实体类源码如下:

public class UserDTO implements Serializable {private Long id;private String username;private String password;private String nickname;private Long phoneNum;// 有效标识:0-无效;1-有效private int enabled;private String email;private String userface;private Timestamp regTime;private Timestamp updateTime;// ......省略各个属性的set和get方法
}
public class CustomUser extends User {private Long id;private String nickname;private Long phoneNum;private List<Role> roles;// 当前角色private Role currentRole;private String email;private String userface;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")private Date regTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")private Date updateTime;public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {super(username, password, authorities);}public CustomUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);}@Override@JsonIgnorepublic List<GrantedAuthority> getAuthorities() {List<GrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleCode()));}return authorities;}// ......省略其他属性的set和get方法}

Mapper层实现根据手机号码查询用户详细信息代码如下:
UserMapper.java

@Repository
public interface UserMapper {UserDTO loadUserByPhoneNum(@Param("phoneNum") Long phoneNum);// ......省略其他抽象方法
}

UserMapper.xml

<select id="loadUserByPhoneNum" resultType="org.sang.pojo.dto.UserDTO">SELECT id, username, nickname,password, phoneNum, enabled, email, userface, regTime, updateTimeFROM `user`WHERE phoneNum = #{phoneNum,jdbcType=BIGINT}
</select>

5 修改短信服务sendLoginVeryCodeMessage方法

关于在SpringBoot项目中如何集成腾讯云短信服务实现发送短信验证码功能,可以参考我之前发表在公众号的文章SpringBoot项目中快速集成腾讯云短信SDK实现手机验证码功能

只是需要稍作修改,因为发短信验证码时要求国内手机号前缀为+86,后面接的是用户的11位手机号码。而我们的数据库中存储的是11位手机号码,使用手机号+短信验证码登录时使用的也是11位手机号码。因此将短信验证码存入redis缓存时需要将这里手机号的+86前缀去掉。

如果这里不改,那么数据库中用户的手机号码字段就要设计成一个字符串类型,前端用户登录时传入的手机号参数也应该加上+86前缀。为了避免更多地方修改,我们就在这里修改好了。

SmsService.java

public SendSmsResponse sendLoginVeryCodeMessage(String phoneNum) {SendSmsRequest req = new SendSmsRequest();req.setSenderId(null);req.setSessionContext(null);req.setSign("阿福谈Java技术栈");req.setSmsSdkAppid(smsProperty.getAppid());req.setTemplateID(SmsEnum.PHONE_CODE_LOGIN.getTemplateId());req.setPhoneNumberSet(new String[]{phoneNum});String verifyCode = getCode();String[] params = new String[]{verifyCode, "10"};req.setTemplateParamSet(params);logger.info("req={}", JSON.toJSONString(req));try {SendSmsResponse res = smsClient.SendSms(req);if ("Ok".equals(res.getSendStatusSet()[0].getCode())) {// 截掉+86字段,发送短信验证码成功则将验证码保存到redis缓存中(目前只针对国内短息业务)phoneNum = phoneNum.substring(3);redisTemplate.opsForValue().set("loginVerifyCode:"+phoneNum, verifyCode, 10, TimeUnit.MINUTES);}return res;} catch (TencentCloudSDKException e) {logger.error("send message failed", e);throw new RuntimeException("send message failed, caused by " + e.getMessage());}// 其他代码省略

6 修改WebSecurityConfig配置类

最后我们需要修改WebSecurityConfig配置类,定义MobilePhoneAuthenticationProviderAuthenticationManager两个类的bean方法,同时在两个configure方法中增加新的逻辑处理。

最后WebSecurityConfig配置类的完整代码如下:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate UserService userService;@ResourceRedisTemplate<String, Object> redisTemplate;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);MobilePhoneAuthenticationProvider mobilePhoneAuthenticationProvider = this.mobilePhoneAuthenticationProvider();auth.authenticationProvider(mobilePhoneAuthenticationProvider);}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 添加手机登录认证过滤器,在构造函数中设置拦截认证请求路径MobilePhoneAuthenticationFilter mobilePhoneAuthenticationFilter = new MobilePhoneAuthenticationFilter("/mobile/login");mobilePhoneAuthenticationFilter.setAuthenticationSuccessHandler(new FormLoginSuccessHandler());mobilePhoneAuthenticationFilter.setAuthenticationFailureHandler(new FormLoginFailedHandler());// 下面这个authenticationManager必须设置,否则在MobilePhoneAuthenticationFilter#attemptAuthentication// 方法中调用this.getAuthenticationManager().authenticate(authRequest)方法时会报NullPointExceptionmobilePhoneAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());mobilePhoneAuthenticationFilter.setAllowSessionCreation(true);http.addFilterAfter(mobilePhoneAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);// 配置跨域http.cors().configurationSource(corsConfigurationSource());// 禁用spring security框架的退出登录,使用自定义退出登录http.logout().disable();http.authorizeRequests().antMatchers("/user/reg").anonymous().antMatchers("/sendLoginVerifyCode").anonymous().antMatchers("/doc.html").hasAnyRole("user", "admin").antMatchers("/admin/**").hasRole("admin")///admin/**的URL都需要有超级管理员角色,如果使用.hasAuthority()方法来配置,需要在参数中加上ROLE_,如下:hasAuthority("ROLE_超级管理员").anyRequest().authenticated()//其他的路径都是登录后即可访问.and().formLogin().loginPage("http://localhost:3000/#/login").successHandler(new FormLoginSuccessHandler()).failureHandler(new FormLoginFailedHandler()).loginProcessingUrl("/user/login").usernameParameter("username").passwordParameter("password").permitAll().and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/blogimg/**","/index.html","/static/**");}@BeanAccessDeniedHandler getAccessDeniedHandler() {return new AuthenticationAccessDeniedHandler();}//配置跨域访问资源private CorsConfigurationSource corsConfigurationSource() {UrlBasedCorsConfigurationSource source =   new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*");  //同源配置,*表示任何请求都视为同源,若需指定ip和端口可以改为如“localhost:8080”,多个以“,”分隔;corsConfiguration.addAllowedHeader("*");//header,允许哪些header,本案中使用的是token,此处可将*替换为token;corsConfiguration.addAllowedMethod("*");   //允许的请求方法,PSOT、GET等corsConfiguration.setAllowCredentials(true);// 注册跨域配置source.registerCorsConfiguration("/**",corsConfiguration); //配置允许跨域访问的urlreturn source;}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic MobilePhoneAuthenticationProvider mobilePhoneAuthenticationProvider() {MobilePhoneAuthenticationProvider mobilePhoneAuthenticationProvider = new MobilePhoneAuthenticationProvider(userService, redisTemplate);return mobilePhoneAuthenticationProvider;}
}

7 验证效果

编码完成后,我们在启动Mysql服务器和Redis服务器后启动我们的SpringBoot项目

首先在Postman中调用发送短信验证码接口


验证码发送成功后返回如下响应信息:

{"status": 200,"msg": "success","data": {"code": "Ok","phoneNumber": "+8618682244076","fee": 1,"message": "send success"}
}

同时手机上也会受到6位短信验证码,有效期10分钟

然后我们使用自己的手机号+收到的6位短信验证码调用登录接口


登录成功后返回如下响应信息:

{"msg": "login success","userInfo": {"accountNonExpired": true,"accountNonLocked": true,"authorities": [{"authority": "ROLE_admin"},{"authority": "ROLE_user"},{"authority": "ROLE_test1"}],"credentialsNonExpired": true,"currentRole": {"id": 1,"roleCode": "admin","roleName": "管理员"},"email": "heshengfu2018@163.com","enabled": true,"id": 3,"nickname": "程序员阿福","phoneNum": 18682244076,"regTime": 1624204813000,"roles": [{"$ref": "$.userInfo.currentRole"},{"id": 2,"roleCode": "user","roleName": "普通用户"},{"id": 3,"roleCode": "test1","roleName": "测试角色1"}],"username": "heshengfu"},"status": "success"
}

到这里,实现在集成SpringSecurity的SpringBoot应用中增加手机号+短信码的方式登录认证的功能也就实现了。各位读者朋友如果觉得文章对你有帮助,欢迎给我的这篇文章点个在看并转发给身边的程序员同事和朋友,谢谢!后面有时间笔者会在前端用户登录界面调用本次实现的后台接口实现手机号+短信验证码功能。

以下是这边文章在本人的gitee仓库的源码地址,需要研究的完整代码的朋友可以克隆到自己本地。

blogserver项目gitee克隆地址: https://gitee.com/heshengfu1211/blogserver.git

本文首发个人微信公众号【阿福谈Web编程】,觉得我的文章对你有帮助或者有什么疑问需要与我进行交流的读者朋友欢迎关注我的微信公众号。关注后可在我的微信公众号菜单栏里点击【联系作者】,就会发送笔者的微信二维码给你。笔者期待在技术精进的路上遇到越来越多同行的盆友,让我们在IT技术学习的路上不孤单!

手把手带你在集成SpringSecurity的SpringBoot应用中添加短信验证码登录认证功能相关推荐

  1. SpringBoot OAuth2.0 使用短信验证码登录授权

    SpringBoot OAuth2.0 使用短信验证码登录授权 实现步骤: 自定义授权器,继承 AbstractTokenGranter 类: 重写 getOAuth2Authentication 函 ...

  2. SpringBoot基于Session实现短信验证码登录

  3. SpringBoot + SpringSecurity 短信验证码登录功能实现

    实现原理 在之前的文章中,我们介绍了普通的帐号密码登录的方式:SpringBoot + Spring Security 基本使用及个性化登录配置(http://www.deiniu.com/artic ...

  4. 最新web/springboot打造通用的短信验证码微服务(详细)

    前言 很久之前的一篇文章, 最新web/java/jsp实现发送手机短信验证码和邮箱验证码的注册登录功能(详细),截止到目前,依然有很多小伙伴,私信需要帮助,于是我再加一篇,让大家能更好的使用.(当然 ...

  5. Spring Security OAuth2 优雅的集成短信验证码登录以及第三方登录

    基于SpringCloud做微服务架构分布式系统时,OAuth2.0作为认证的业内标准,Spring Security OAuth2也提供了全套的解决方案来支持在Spring Cloud/Spring ...

  6. spring-security学习(六)——短信验证码登录

    文章目录 前言 spring-security认证流程 自定义短信验证码登录 总结 前言 前面几篇博客完成了验证码的重构和说明,这一篇开始介绍自定义的短信验证码登录. spring-security认 ...

  7. [SpringBoot]使用token 短信验证码 Redis的功能实现基本的登陆注册操作(含Redis token 验证码如何配置)

    浅谈对于登录注册业务的思路以及部分代码的实现 近期开始了与移动端项目的开发,由于我个人是后台方面的所以肯定避免不了要实现每个app都应该具有的功能:登录与注册,我一开始写也是感觉非常的没有头绪,所以这 ...

  8. SpringBoot 实现手机发送短信验证码

    手机发送短信 内容 一.手机发送短信 1. 前端界面代码 2. UserInfoController 控制器 3. application.properties 配置类文件 4. 具体实现 总结 内容 ...

  9. 手把手带你撸一把springsecurity框架源码中的认证流程

    提springsecurity之前,不得不说一下另外一个轻量级的安全框架Shiro,在springboot未出世之前,Shiro可谓是颇有统一J2EE的安全领域的趋势. 有关shiro的技术点 1.s ...

最新文章

  1. Web安全***测试之信息搜集篇
  2. 2019牛客暑期多校训练营(第九场)-E All men are brothers
  3. SQLAlchemy中模糊查询;JS中POST带参数跳转;JS获取url参数
  4. 分布式系统的开发经验与心得
  5. 一.因子图优化学习---董靖博士在深蓝学院的公开课学习(1)
  6. 三家快递公司涨派费:9月1日起每票上调0.1元
  7. 56. 合并区间(javascript)
  8. 4创建ui显示不出来_4道小学生经典推理题,家长们一道也做不出来,太烧脑了...
  9. nginx代理tomcat,https
  10. 【Xamarin.Android】探索android的底部导航视图
  11. 简单实现手机号验证码注册功能
  12. VBlog项目代码理解之后端
  13. Java 删除session实现退出登录
  14. [乡土民间故事_徐苟三传奇]第二回_巧答言长工骂财主
  15. error: File: XX 520.13 MB, exceeds 100.00 MB以上大文件导致push失败解决方法
  16. 网络请求框架 -- 理解Https
  17. ddr4 lpddr4区别_笔记本内存LPDDR3就一定不如DDR4吗?宏旺半导体解释两者的区别?...
  18. Android图表控件MPAndroidChart——BarChart实现多列柱状图以及堆积柱状图
  19. 计算机的音乐数字要全是数字,数字中的音乐
  20. 计算机一级云居寺,刁常宇-Zhejiang University Personal homepage

热门文章

  1. Java工程师岗位职责有哪些 职业发展前景怎么样
  2. Adobe Acrobat Pro DC 2021下载及教程
  3. Win系统 - 压缩包part1与part2之间的关系
  4. arch linux对32amd,AMDGPU (简体中文)
  5. win10搭建代理服务器实现绕过校园网的共享限制--从入门到放弃
  6. 分析研究一战到底节目规则演变
  7. C++进阶——反向迭代器Reverse_iterator
  8. 视频二维码的功能如何实现?
  9. MERCURY无线面板式AP-强电供电-WiFi密码-IP地址
  10. 基于CGE模型-我国低碳经济发展政策模拟分析:CGE-动态论文、代码以及数据