自定义用户认证逻辑
处理用户信息获取逻辑
实现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(); // 暂时将防护跨站请求伪造的功能置为不可用
}
}
源码解析
略
原文地址:https://www.cnblogs.com/okokabcd/p/9770342.html
时间: 2024-10-01 02:17:07