(一)用户信息获取
<1>UserDetailsService
这是SpringSecurity提供的一个接口,用于根据登录名获取用户信息(从内存,数据库中…)。
<2>实现接口UserDetailsService
详解UserDetails请参考附录。
@Component
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据username读取数据库的用户对象,这边省略用固定值代替
return new User(username, "1", AuthorityUtils.createAuthorityList("admin","ROLE_USER"));//含用户名,密码,权限的用户信息
}
}
(二)密码加密处理
<1>声明一个PasswordEncoder的bean。
@Configuration
public class SecurityCoreConfig {
//配置了该bean之后,spring security校验用户密码时会用该加密算法进行校验(而不是明文)
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();//可用自定义的加密方式(实现PasswordEncoder接口即可)
}
}
<2>修改MyUserDetailsService
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据username读取数据库的用户对象,读取用户的密码,读取用户的权限,这边省略用固定值代替
String encoderPwd = passwordEncoder.encode("1");//encoderPwd本应该从数据库读入,这里仅示范
System.out.println("加密后的密码为:"+encoderPwd);
//第一次加密后的密码为:$2a$10$6AE4VuHHRbDC7DLLJ/DVIecZ8kwSoVDlLwh4KiMJFjpSu/qmciBBS
//第二次加密后的密码为:$2a$10$j4LNVlGzONVgRQJRVgAU0u3cAN4CjiC60Ia02Q7rKpCMJnkkwhn7e
//相同字符串加密后的密码串不同,因为spring security使用一个密钥(site-wide secret)以及8位随机盐对原密码进行加密。
return new User(username, encoderPwd, AuthorityUtils.createAuthorityList("admin","ROLE_USER"));
//此时若返回明文密码的user是会报密码错误的,spring security会用页面上加密后的密码和所返回User的密码进行比对
}
}
(三)自定义登录页面
<1>登录页html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>账户密码登陆界面</p>
<form action="/accessIn/form" method="post">
<p>
<label>Username</label> <input type="text" name="username" id="username" >
</p>
<p>
<label>Password</label> <input type="password" name="password" id="password">
</p>
<input type="submit" value="Login">
</form>
</body>
</html>
<2>自定义登录页判断
如果是html就跳转到默认登录页面,否则给出提示。
@RestController
public class BrowserSecurityController {
//重定向
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
Logger logger = LoggerFactory.getLogger(BrowserSecurityController.class);
/**
* 认证跳转页面
* @param request
* @param response
* @return
*/
@RequestMapping("/authentication/login")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public String loginPage(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().getLoginUrl());
}
}
return "当前请求需要授权认证,请登录!";
}
}
SpringSecurity配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//表单登陆(未配置登录页,采取默认页面)
.loginPage("/authentication/login")//指定登录路径
.loginProcessingUrl("/accessIn/form")//指定登录请求地址,
//指定的请求会由UsernamePasswordAuthenticationFilter进行处理
.and()
.authorizeRequests()//对请求授权
.antMatchers("/authentication/login","/login.html")
//若不对"/authentication/login"放行,由于后面会对所有请求做身份认证,
//因此会跳回loginPage,导致重定向过多的错误
.permitAll()
.anyRequest()
.authenticated()//进行身份认证
.csrf().disable();
}
}
(四)自定义登录后处理
<1>自定义登录成功处理
继承Spring默认的成功处理器SavedRequestAwareAuthenticationSuccessHandler(拥有默认的实现:跳转)
或者实现接口AuthenticationSuccessHandler。
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {//implements AuthenticationSuccessHandler 或者实现该接口
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
//根据配置获取返回结果的形式,是返回json数据还是用spring的默认实现
if (//...这里省略返回json的具体判断逻辑) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
<2>自定义登录失败处理
继承Spring默认的成功处理器SimpleUrlAuthenticationFailureHandler (拥有默认的实现:跳转)
或者实现接口AuthenticationFailureHandler。
@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {//implements AuthenticationFailureHandler
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
logger.info("登录失败");
//根据配置获取返回结果的形式,是返回json数据还是用spring的默认实现
if (//...这里省略返回json的具体判断逻辑) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(exception.getMessage())));
}else{
super.onAuthenticationFailure(request, response, exception);
}
}
}
SpringSecurity配置两个自定义处理器
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//表单登陆(未配置登录页,采取默认页面)
.loginPage("/login.html")//指定登录页面
.loginProcessingUrl("/accessIn/form")//指定登录请求地址,
//指定的请求会由UsernamePasswordAuthenticationFilter进行处理
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
//...省略,前文有代码;
}
}
(五)页面效果及总结
注册一个PasswordEncoder类型的bean,SpringSecurity默认会将页面的密码用该加密方式加密再与数据库中的密码比对。
成功处理器和失败处理器分别对应登录成功/失败后的相关处理。
注:
具体鉴权原理和流程可参考 Spring Security渐入佳境(一)[附] --SpringSecurity的基本原理及源码剖析。