自定义用户认证逻辑

处理用户信息获取逻辑

实现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. flink内存模型详解与案例

    任务提交时的一些yarn设置(通用客户端模式) 指定并行度                        -p 5 \ 指定yarn队列                     -Dyarn.appl ...

  2. Asp.Net Core之Identity应用(上篇)

    一.前言 在前面的篇章介绍中,简单介绍了IdentityServer4持久化存储机制相关配置和操作数据,实现了数据迁移,但是未对用户实现持久化操作说明.在总结中我们也提到了, 因为IdentitySe ...

  3. 前端性能优化(Application Cache篇)

    正巧看到在送书,于是乎找了找自己博客上记录过的一些东西来及其无耻的蹭书了~~~ 小广告:更多内容可以看我的博客 之前在segmentfault上刷问题看到一个关于manifest的问题,很好奇就研究了 ...

  4. React 和 ES6 工作流之 Webpack的使用(第六部分)

    这是React和ECMAScript2015系列文章的最后一篇,我们将继续探索React 和 Webpack的使用. 下面是所有系列文章章节的链接: React . ES6 - 介绍(第一部分) Re ...

  5. PAT B1015德才论

    题目描述: 宋代史学家司马光在<资治通鉴>中有一段著名的"德才论":"是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人.凡取人之术,苟不 ...

  6. JavaScript实现表单的校验以及匹配正则表达式

    运行效果: 未填写信息报错: 匹配正则表达式: 信息校验无误: 源代码如下: 1 <!DOCTYPE html> 2 <html lang="zh"> 3 ...

  7. C# 一个基于.NET Core3.1的开源项目帮你彻底搞懂WPF框架Prism

    --概述 这个项目演示了如何在WPF中使用各种Prism功能的示例.如果您刚刚开始使用Prism,建议您从第一个示例开始,按顺序从列表中开始.每个示例都基于前一个示例的概念. 此项目平台框架:.NET ...

  8. tracert命令简述

    1. 路由跟踪在线Tracert工具说明 Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据报访问目标所采取的路径.Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP ...

  9. 深度理解springboot集成cache缓存之源码解析

    一.案例准备 1.创建数据表(employee表) 2.创建Employee实体类封装数据库中的数据 @AllArgsConstructor @NoArgsConstructor @Data @ToS ...

  10. Python数据展示 - 生成表格图片

    前言 前一篇文章介绍了推送信息到企业微信群里,其中一个项目推送的信息是使用Python自动生成的表格,本文来讲讲如何用Python生成表格图片. 选一个合适库 Python最大的优点就是第三方库丰富, ...