自定义用户认证逻辑

处理用户信息获取逻辑

实现UserDetailsService接口

@Service
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass()); @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("根据用户名查找用户信息,登录用户名:" + username);
// 从数据库查询相关的密码和权限,这里返回一个假的数据
// 用户名,密码,权限
return new User(username,
"123456",
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}

处理用户校验逻辑

UserDetails接口的一些方法,封装了登录时的一些信息

public interface UserDetails extends Serializable {
/** 权限信息
* Returns the authorities granted to the user. Cannot return <code>null</code>.
*
* @return the authorities, sorted by natural key (never <code>null</code>)
*/
Collection<? extends GrantedAuthority> getAuthorities(); /** 密码
* Returns the password used to authenticate the user.
*
* @return the password
*/
String getPassword(); /** 登录名
* Returns the username used to authenticate the user. Cannot return <code>null</code>
* .
*
* @return the username (never <code>null</code>)
*/
String getUsername(); /** 账户是否过期
* Indicates whether the user's account has expired. An expired account cannot be
* authenticated.
*
* @return <code>true</code> if the user's account is valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
boolean isAccountNonExpired(); /** 账户是否被锁定(冻结)
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
*
* @return <code>true</code> if the user is not locked, <code>false</code> otherwise
*/
boolean isAccountNonLocked(); /** 密码是否过期
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
*
* @return <code>true</code> if the user's credentials are valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
boolean isCredentialsNonExpired(); /** 账户是否可用(删除)
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
*
* @return <code>true</code> if the user is enabled, <code>false</code> otherwise
*/
boolean isEnabled();
}

返回数据写成

return new User(username, // 用户名
"123456", // 密码
true, // 是否可用
true, // 账号是否过期
true, // 密码是否过期
true, // 账号没有被锁定标志
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

处理密码加密解密

PasswordEncoder接口

public interface PasswordEncoder {

	/** 加密
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
* greater hash combined with an 8-byte or greater randomly generated salt.
*/
String encode(CharSequence rawPassword); /** 判断密码是否匹配
* Verify the encoded password obtained from storage matches the submitted raw
* password after it too is encoded. Returns true if the passwords match, false if
* they do not. The stored password itself is never decoded.
*
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword); }

在BrowerSecurityConfig中配置PasswordEncoder

// 配置PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

MyUserDetailsService.java改成

// 注入passwordEncoder
@Autowired
private PasswordEncoder passwordEncoder; // 返回写成这样
return new User(username, // 用户名
passwordEncoder.encode("123456"), // 这个是从数据库中读取的已加密的密码
true, // 是否可用
true, // 账号是否过期
true, // 密码是否过期
true, // 账号没有被锁定标志
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

个性化用户认证流程

自定义登录页面

修改BrowserSecurityConfig类

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
// 配置PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("BrowserSecurityConfig");
http.formLogin() // 表单登录
.loginPage("/sign.html") // 自定义登录页面URL
.loginProcessingUrl("/authentication/form") // 处理登录请求的URL
.and()
.authorizeRequests() // 对请求做授权
.antMatchers("/sign.html").permitAll() // 登录页面不需要认证
.anyRequest() // 任何请求
.authenticated() // 都需要身份认证
.and().csrf().disable(); // 暂时将防护跨站请求伪造的功能置为不可用
}
}

问题

  1. 不同的登录方式,通过页面登录,通过app登录
  2. 给多个应用提供认证服务,每个应用需要的自定义登录页面

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties; // 配置PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("BrowserSecurityConfig");
http.formLogin() // 表单登录
.loginPage("/authentication/require") // 自定义登录页面URL
.loginProcessingUrl("/authentication/form") // 处理登录请求的URL
.and()
.authorizeRequests() // 对请求做授权
.antMatchers("/authentication/require",
securityProperties.getBrowser().getLoginPage())
.permitAll() // 登录页面不需要认证
.anyRequest() // 任何请求
.authenticated() // 都需要身份认证
.and().csrf().disable(); // 暂时将防护跨站请求伪造的功能置为不可用
}
}

BrowserSecurityController判断访问的url如果以.html结尾就跳转到登录页面,否则就返回json格式的提示信息

@RestController
public class BrowserSecurityController {
private Logger logger = LoggerFactory.getLogger(getClass()); private RequestCache requestCache = new HttpSessionRequestCache(); private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Autowired
private SecurityProperties securityProperties; /**
* 需要身份认证时,跳转到这里
*
* @param request
* @param response
* @return
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse requireAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引发跳转请求的url是:" + targetUrl);
if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
redirectStrategy.sendRedirect(request, response,
securityProperties.getBrowser().getLoginPage());
}
}
return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页");
}
}

自定义登录成功处理

AuthenticationSuccessHandler接口,此接口登录成功后会被调用

@Component
public class ImoocAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(ImoocAuthenticationSuccessHandler.class); @Autowired
private ObjectMapper objectMapper; @Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
logger.info("登录成功");
// 登录成功后把authentication返回给前台
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}

自定义登录失败处理

@Component
public class ImoocAuthenticationFailHandler implements AuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired
private ObjectMapper objectMapper; @Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e)
throws IOException, ServletException {
logger.info("登录失败");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
}
}

问题

  • 登录成功或失败后返回页面还是json数据格式

登录成功后的处理

@Component
public class ImoocAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(ImoocAuthenticationSuccessHandler.class); @Autowired
private ObjectMapper objectMapper; @Autowired
private SecurityProperties securityProperties; @Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
logger.info("登录成功");
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
// 登录成功后把authentication返回给前台
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}

登录失败后的处理

@Component
public class ImoocAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired
private ObjectMapper objectMapper; @Autowired
private SecurityProperties securityProperties; @Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e)
throws IOException, ServletException {
logger.info("登录失败");
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
} else {
super.onAuthenticationFailure(request, response, e);
}
}
}

认证流程源码级详解

认证处理流程说明

认证结果如何在多个请求之间共享

一个请求进来的时候,先检查context是否存有该请求的认证信息

获取认证用户信息

图片验证码

生成图片验证码

  1. 根据随机数生成图片
  2. 将随机数存到Session中
  3. 在将生成的图片写到接口的响应中

图片验证码重构

验证码基本参数可配置

验证码图片的宽,高,字符数,失效时间可配置(注意字符数和失效时间不要在请求级配置中)。请求级配置就是在请求验证码时/code/image?width=100&height=30,应用级配置就是在应用的配置文件中

// 在使用这些配置时,如果请求级配置有就用请求级配置,否则就依次用应用级配置,默认配置
int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width",
securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request.getRequest(), "height",
securityProperties.getCode().getImage().getHeight());

验证码拦截的接口可配置

默认情况下,只有在注册,登录的需要验证码的时候才拦截的,如果还有其他情景下需要则能够在不修改依赖的情况下可配置.如何实现呢,在配置文件中添加要需要验证码的url,验证码的验证是通过过滤器实现的,那么在对其过滤的时候判断当前url是否是需要拦截即可

验证码的生成逻辑可配置

把生成验证码的功能定义成接口,框架给出一个默认的实现,如果应用不定义就用这个默认实现,如果应用要定制一个,就实现这个接口就可以了.

// 框架中的默认实现不加注释@Component进行初始化,用如下方式对其进行初始化
// 检测上下文环境中是否有imageCodeGenerator这个bean,如果没有就初始化框架中提供的默认实现
@Configuration
public class ValidateCodeBeanConfig { @Autowired
private SecurityProperties securityProperties; @Bean
@ConditionalOnMissingBean(name = "imageCodeGenerator")
public ValidateCodeGenerator imageCodeGenerator() {
System.out.println("init imageCodeGenerator");
ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
codeGenerator.setSecurityProperties(securityProperties);
return codeGenerator;
}
}

添加记住我功能

基本原理

具体实现

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
// 用来读取配置
@Autowired
private SecurityProperties securityProperties; // 登录成功后的处理
@Autowired
private ImoocAuthenticationSuccessHandler imoocAuthenticationSuccessHandler; // 登录失败后的处理
@Autowired
private ImoocAuthenticationFailHandler imoocAuthenticationFailHandler; @Autowired
private DataSource dataSource; @Autowired
private UserDetailsService userDetailsService; // 配置PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} // 用于remember me
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// tokenRepository.setCreateTableOnStartup(true); // 启动时创建表
return tokenRepository;
} @Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("BrowserSecurityConfig");
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.afterPropertiesSet(); http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin() // 表单登录
.loginPage("/authentication/require") // 自定义登录页面URL
.loginProcessingUrl("/authentication/form") // 处理登录请求的URL
.successHandler(imoocAuthenticationSuccessHandler) // 登录成功后的处理
.failureHandler(imoocAuthenticationFailHandler) // 登录失败后的处理
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
.userDetailsService(userDetailsService)
.and()
.authorizeRequests() // 对请求做授权
.antMatchers("/authentication/require",
securityProperties.getBrowser().getLoginPage(),
"/code/image")
.permitAll() // 登录页面不需要认证
.anyRequest() // 任何请求
.authenticated() // 都需要身份认证
.and().csrf().disable(); // 暂时将防护跨站请求伪造的功能置为不可用
}
}

源码解析

Spring Security之用户名+密码登录的更多相关文章

  1. Spring Security 入门(1-3-1)Spring Security - http元素 - 默认登录和登录定制

    登录表单配置 - http 元素下的 form-login 元素是用来定义表单登录信息的.当我们什么属性都不指定的时候 Spring Security 会为我们生成一个默认的登录页面. 如果不想使用默 ...

  2. Spring Security 实现手机验证码登录

    思路:参考用户名密码登录过滤器链,重写认证和授权 示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出): 由于涉及内容较多,建议先复制到本地工程中,然后在细细研究. 1. ...

  3. spring security 动态 修改当前登录用户的 权限

    1.前言 spring security 可以获取当前登录的用户信息,同时提供了接口 来修改权限列表信息 , 使用这个方法 ,可以动态的修改当前登录用户权限. 那么问题来了... 如果我是管理员 ,如 ...

  4. Spring Security之多次登录失败后账户锁定功能的实现

    在上一次写的文章中,为大家说到了如何动态的从数据库加载用户.角色.权限信息,从而实现登录验证及授权.在实际的开发过程中,我们通常会有这样的一个需求:当用户多次登录失败的时候,我们应该将账户锁定,等待一 ...

  5. spring security实现记录用户登录时间等信息

    目录 spring security实现记录用户登录时间等信息 一.原理分析 二.实现方式 2.1 自定义AuthenticationSuccessHandler实现类 2.2 在spring-sec ...

  6. Spring Security默认的用户登录表单 页面源代码

    Spring Security默认的用户登录表单 页面源代码 <html><head><title>Login Page</title></hea ...

  7. Spring Security OAuth2 SSO 单点登录

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

  8. Python用户名密码登录系统(MD5加密并存入文件,三次输入错误将被锁定)及对字符串进行凯撒密码加解密操作

    # -*- coding: gb2312 -*- #用户名密码登录系统(MD5加密并存入文件)及对字符串进行凯撒密码加解密操作 #作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.co ...

  9. pyhton学习,day1作业,用户名密码登录模块

    要求,通过用户名密码登录,登录错误3次,锁定用户名 # coding=utf-8 # Author: RyAn Bi import os, sys #调用系统自己的库 accounts_file = ...

随机推荐

  1. Spring官宣网传大漏洞,并提供解决方案

    Spring沦陷了!这样的标题这几天是不是看腻了?然而,仔细看看都是拿着之前的几个毫不相干的CVE来大吹特吹.所以,昨天发了一篇关于最近网传的Spring大漏洞的文章,聊了聊这些让人迷惑的营销文.以及 ...

  2. 你不需要基于 CSS Grid 的栅格布局系统

    在过去的几个星期里,我开始看到基于 CSS Grid 的布局框架和栅格系统的出现.我们惊讶它为什么出现的这么晚.但除了使用 CSS Grid 栅格化布局,我至今还没有看到任何框架能提供其他有价值的东西 ...

  3. css布局基础总结

    前端css布局知识繁杂,实现方式多种多样.想写出高效.合理的布局,必须以深厚的css基础为前提.为了方便记忆和复习,将css布局要点记录如下.内容较多,应用方面说的不太详细,但都是很实用的点. 所谓布 ...

  4. 记一次曲折的CVE-2018-1270复现分析

    前言 前两天接到朋友对某个授权目标的漏扫结果,也算是初次接触到这个漏洞,就想着顺手分析一下复现一下,因为分析这个漏洞的文章也比较少,所以刚开始比较迷,进度也比较慢. 漏洞复现 使用vulhub搭建环境 ...

  5. 使用pyttsx3实现简单tts服务

    操作系统:Windows 10_x64 python版本:Python 3.9.2_x64 pyttsx3版本: 2.90   pyttsx3是一个tts引擎包装器,可对接SAPI5.NSSS(NSS ...

  6. 攻防世界——gif

    分析 只有黑白两种颜色,大小均一样.考虑代表着二进制. python脚本 ''' 同样颜色的图片的二进制数据都相同 编写思路:取二进制 -> 转ascii码 ''' white = open(r ...

  7. npm 和 Yarn 镜像站配置

    Node.js 作为近年来非常受欢迎的 Web 开发运行环境,由于开发者众多,贡献开源代码的人也很多,所有这些凝结成了 npm 这个世界上最大的软件包仓库,但是受限于 npm 软件包的服务器在国外,国 ...

  8. 帝国cms修改成https后后台登陆空白的解决办法

    以下方法适用帝国cms7.5版本: 7.5版本已经有了http和https自动识别,但是因为一些疑难杂症的原因,自动识别判断的不准,后台登录也是空白, 我们可以打开e/config.php查找'htt ...

  9. 帝国CMS批量提取正文内容到简介

    最近接到一个帝国CMS模板改版项目,自带的数据可能是采集的,以前的简介字段内容只截取了60个字,新模板的简介60字符太少了,不美观,想让简介都截取200个字,怎么批量修改呢,文章太多了手动改肯定不行, ...

  10. LAN交换机自学习算法

    LAN交换机自学习算法 提示 第二层交完全忽略帧的数据部分协议,仅根据第二层以太网的MAC地址做出转发决策. MAC地址表有时又被称作内容可编址内存(CAM)表 检查源MAC地址 如果源MAC地址不存 ...