自定义用户
说明
- 用户名和密码一般存储于数据库中,表单输入的用户名和密码与数据库查询出来的用户名和密码进行比对
- 以下例子为SpringSecurity5.x
实现UserDetailsService接口
@Service public class UserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //此处根据用户名在数据库中查找,这里不再查找,直接返回一个org.springframework.security.core.userdetails.User对象(如果是自定义的User类,需要实现UserDetails接口) return new User(username,new BCryptPasswordEncoder().encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
- AuthorityUtils.commaSeparatedStringToAuthorityList() 方法模拟一个admin的权限,该方法可以将逗号分隔的字符串转换为权限集合(理解)
- 用户认证流程
从数据库中查找用户信息,如果为空,则抛出异常,否则返回一个用户对象,再由系统提供的DaoAuthenticationProvider类去比对密码是否正确 - 如果自己编写User实体类,需要实现UserDetails 接口
编写配置类
@Configuration public class BrowserSecurity extends WebSecurityConfigurerAdapter { //引入自定义UserDetailsService @Autowired private UserService userService; //加密 @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } //配置内存认证 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //设置UserDetailsService以及密码规则 auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } }
UserDetails接口简介
自定义User类实现该接口,该接口主要有如下方法
- getAuthorities():获取当前用户对象的角色信息
- getPassword(): 获取密码,与用户输入的密码进行比对
- getUsername: 获取当前用户对象的用户名
- isAccountNonExpired():当前用户是否未过期,可自定义逻辑判断,如果返回false,会抛出AccountExpiredException
- isAccountNonLocked() :当前用户是否未锁定,可自定义逻辑判断
- isCredentialsNonExpired(): 当前账户密码是否未过期
- isEnabled(): 当前账户是否可用
自定义登录页
说明
- 在表单登录中一般是采用自己的登录页面
- 需要在SpringSecurity中配置相关设置
一个简单的登录页面
- 在 resources/static 目录下编写一个登录页面
myLogin.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>自定义登录页面登录</title> </head> <body> <form action="/login" method="post"> <div class="form"> <h3>账户登录</h3> <input type="text" placeholder="用户名" name="username" required="required" /></br> <input type="password" placeholder="密码" name="password" required="required" /> <button type="submit">登录</button> </div> </form> </body> </html>
在自定义用户的基础上增加配置
整体配置如下:
@Configuration public class BrowserSecurity extends WebSecurityConfigurerAdapter { //引入自定义UserDetailsService @Autowired private UserService userService; //加密 @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } //配置内存认证 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //设置UserDetailsService以及密码规则 auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } //配置HttpSecurity @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //授权配置 .antMatchers("/myLogin.html").permitAll().anyRequest().authenticated() .and() //表单配置 .formLogin().loginPage("/myLogin.html").loginProcessingUrl("/login") .and() //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉 .csrf().disable(); } }
结果
除了登录页面可以直接访问,其他请求需要跳转到登录页面完成认证后才可以访问
自定义登录成功和失败
修改表单登录配置
整体配置如下:
@Configuration public class BrowserSecurity extends WebSecurityConfigurerAdapter { //引入自定义UserDetailsService @Autowired private UserService userService; //加密 @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } //配置内存认证 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //设置UserDetailsService以及密码规则 auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } //配置HttpSecurity @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //授权配置 .antMatchers("/myLogin.html").permitAll().anyRequest().authenticated() .and() //表单配置 .formLogin().loginPage("/myLogin.html").loginProcessingUrl("/login") .successHandler(new AuthenticationSuccessHandler() { //登录成功返回一段json信息 @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //Authentication authentication 包含用户登录信息 String name = authentication.getName(); response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); response.setStatus(200); Map<String,Object> map = new HashMap<>(); map.put("status",200); map.put("msg",name); ObjectMapper mapper = new ObjectMapper(); out.write(mapper.writeValueAsString(map)); out.flush(); out.close(); } }) .failureHandler(new AuthenticationFailureHandler() { //登录失败,根据相关异常返回失败信息 @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); response.setStatus(401); Map<String,Object> map = new HashMap<>(); map.put("status",401); if(e instanceof LockedException){ map.put("msg","账户被锁定"); }else if(e instanceof BadCredentialsException){ map.put("msg","账户名或密码错误"); }else if(e instanceof DisabledException){ map.put("msg","账户被禁用"); }else if(e instanceof AccountExpiredException){ map.put("msg","账户已过期"); }else if(e instanceof CredentialsExpiredException){ map.put("msg","密码已过期"); }else{ map.put("msg","登录失败"); } ObjectMapper mapper = new ObjectMapper(); out.write(mapper.writeValueAsString(map)); out.flush(); out.close(); } }) .and() //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉 .csrf().disable(); } }
也可以自定义类实现接口的方式
- 成功登录:实现org.springframework.security.web.authentication.AuthenticationSuccessHandler接口的onAuthenticationSuccess方法
@Component public class MyAuthenticationSucessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); response.getWriter().write(mapper.writeValueAsString(authentication)); } }
- 登录失败: 实现
org.springframework.security.web.authentication.AuthenticationFailureHandler的onAuthenticationFailure方法
@Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { } }
- 之后在配置类中引入
@Autowired private MyAuthenticationSucessHandler authenticationSucessHandler; @Autowired private MyAuthenticationFailureHandler authenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登录 .successHandler(authenticationSucessHandler) // 处理登录成功 .failureHandler(authenticationFailureHandler) // 处理登录失败 }
结果
登录成功
登录失败