Spring Security之用户名+密码登录
自定义用户认证逻辑
处理用户信息获取逻辑
实现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(); // 暂时将防护跨站请求伪造的功能置为不可用
}
}
问题
- 不同的登录方式,通过页面登录,通过app登录
- 给多个应用提供认证服务,每个应用需要的自定义登录页面

@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是否存有该请求的认证信息

获取认证用户信息

图片验证码
生成图片验证码
- 根据随机数生成图片
- 将随机数存到Session中
- 在将生成的图片写到接口的响应中
图片验证码重构
验证码基本参数可配置

验证码图片的宽,高,字符数,失效时间可配置(注意字符数和失效时间不要在请求级配置中)。请求级配置就是在请求验证码时/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之用户名+密码登录的更多相关文章
- Spring Security 入门(1-3-1)Spring Security - http元素 - 默认登录和登录定制
登录表单配置 - http 元素下的 form-login 元素是用来定义表单登录信息的.当我们什么属性都不指定的时候 Spring Security 会为我们生成一个默认的登录页面. 如果不想使用默 ...
- Spring Security 实现手机验证码登录
思路:参考用户名密码登录过滤器链,重写认证和授权 示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出): 由于涉及内容较多,建议先复制到本地工程中,然后在细细研究. 1. ...
- spring security 动态 修改当前登录用户的 权限
1.前言 spring security 可以获取当前登录的用户信息,同时提供了接口 来修改权限列表信息 , 使用这个方法 ,可以动态的修改当前登录用户权限. 那么问题来了... 如果我是管理员 ,如 ...
- Spring Security之多次登录失败后账户锁定功能的实现
在上一次写的文章中,为大家说到了如何动态的从数据库加载用户.角色.权限信息,从而实现登录验证及授权.在实际的开发过程中,我们通常会有这样的一个需求:当用户多次登录失败的时候,我们应该将账户锁定,等待一 ...
- spring security实现记录用户登录时间等信息
目录 spring security实现记录用户登录时间等信息 一.原理分析 二.实现方式 2.1 自定义AuthenticationSuccessHandler实现类 2.2 在spring-sec ...
- Spring Security默认的用户登录表单 页面源代码
Spring Security默认的用户登录表单 页面源代码 <html><head><title>Login Page</title></hea ...
- Spring Security OAuth2 SSO 单点登录
基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...
- Python用户名密码登录系统(MD5加密并存入文件,三次输入错误将被锁定)及对字符串进行凯撒密码加解密操作
# -*- coding: gb2312 -*- #用户名密码登录系统(MD5加密并存入文件)及对字符串进行凯撒密码加解密操作 #作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.co ...
- pyhton学习,day1作业,用户名密码登录模块
要求,通过用户名密码登录,登录错误3次,锁定用户名 # coding=utf-8 # Author: RyAn Bi import os, sys #调用系统自己的库 accounts_file = ...
随机推荐
- flink内存模型详解与案例
任务提交时的一些yarn设置(通用客户端模式) 指定并行度 -p 5 \ 指定yarn队列 -Dyarn.appl ...
- Asp.Net Core之Identity应用(上篇)
一.前言 在前面的篇章介绍中,简单介绍了IdentityServer4持久化存储机制相关配置和操作数据,实现了数据迁移,但是未对用户实现持久化操作说明.在总结中我们也提到了, 因为IdentitySe ...
- 前端性能优化(Application Cache篇)
正巧看到在送书,于是乎找了找自己博客上记录过的一些东西来及其无耻的蹭书了~~~ 小广告:更多内容可以看我的博客 之前在segmentfault上刷问题看到一个关于manifest的问题,很好奇就研究了 ...
- React 和 ES6 工作流之 Webpack的使用(第六部分)
这是React和ECMAScript2015系列文章的最后一篇,我们将继续探索React 和 Webpack的使用. 下面是所有系列文章章节的链接: React . ES6 - 介绍(第一部分) Re ...
- PAT B1015德才论
题目描述: 宋代史学家司马光在<资治通鉴>中有一段著名的"德才论":"是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人.凡取人之术,苟不 ...
- JavaScript实现表单的校验以及匹配正则表达式
运行效果: 未填写信息报错: 匹配正则表达式: 信息校验无误: 源代码如下: 1 <!DOCTYPE html> 2 <html lang="zh"> 3 ...
- C# 一个基于.NET Core3.1的开源项目帮你彻底搞懂WPF框架Prism
--概述 这个项目演示了如何在WPF中使用各种Prism功能的示例.如果您刚刚开始使用Prism,建议您从第一个示例开始,按顺序从列表中开始.每个示例都基于前一个示例的概念. 此项目平台框架:.NET ...
- tracert命令简述
1. 路由跟踪在线Tracert工具说明 Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据报访问目标所采取的路径.Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP ...
- 深度理解springboot集成cache缓存之源码解析
一.案例准备 1.创建数据表(employee表) 2.创建Employee实体类封装数据库中的数据 @AllArgsConstructor @NoArgsConstructor @Data @ToS ...
- Python数据展示 - 生成表格图片
前言 前一篇文章介绍了推送信息到企业微信群里,其中一个项目推送的信息是使用Python自动生成的表格,本文来讲讲如何用Python生成表格图片. 选一个合适库 Python最大的优点就是第三方库丰富, ...