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 = ...
随机推荐
- 基本类型数组转List
基本类型数组转List 小数 double[] src = {1.1,2.1,3.1}; List<Double> list = Arrays.stream( src ).boxed(). ...
- CAN总线系列讲座第五讲——CAN总线硬件电路设计
CAN总线系列讲座第五讲--CAN总线硬件电路设计一 实战学习背景 CAN总线节点的硬件构成方案有两种,其中的方案:(1).MCU控制器+独立CAN控制器+CAN收发器.独立CAN控制器如SJA10 ...
- Numpy怎样将数组读写到文件
Numpy怎样将数组读写到文件 本文档介绍的是Numpy以自己内建二进制的方式,将数组写出到文件,以及从文件加载数组: 如果是文本.表格类数据,一般使用pandas这个类库做加载和处理,不用numpy ...
- C与C++的区别之函数调用堆栈
函数调用栈 1.函数参数带入(入调用方函数的栈,从右向左入栈) int fun(int a); int fun(int a, int b); int fun(int a, int b, int c); ...
- [C/C++基础知识] main函数的参数argc和argv
该篇文章主要是关于C++\C语言最基础的main函数的参数知识,是学习C++或C语言都必备的知识点.不知道你是否知道该知识?希望对大家有所帮助.一.main()函数参数通常我们在写主函数时都是void ...
- (Math.round(num*100)/100).toFixed(2); 将输入的数字变成保留两位小数
<input type="number" @input="onInputPrice" @blur="onPrice" data-id= ...
- 前端面试题整理——手写方法解析URL参数
//拆分字符串形式 function queryToObj() { const res = {} const search = location.search.substr(1);//去掉前面的&qu ...
- python-爬楼梯
[题目描述] 假设一段楼梯共n(n>1)个台阶,小朋友一步最多能上3个台阶,那么小朋友上这段楼梯一共有多少种方法. [练习要求]请给出源代码程序和运行测试结果,源代码程序要求添加必要的注释. [ ...
- java中downcast向下转型到底有什么用
What is the point of downcast? 当一个方法只有子类才有,马克-to-win:不是说基类和子类都有,开始时又是基类指针指向派生类,这时就需要downcast, see th ...
- 截取url传值
// 页面传值 subStr(url) { var obj = {}; var str = url.split('?')[1]; var str2 = str.split('&'); cons ...