Spring Security之用户名+密码登录

笑着哭i 提交于 2019-11-30 07:13:12

自定义用户认证逻辑

处理用户信息获取逻辑

实现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(); // 暂时将防护跨站请求伪造的功能置为不可用     } } 

问题

  1. 不同的登录方式,通过页面登录,通过app登录
  2. 给多个应用提供认证服务,每个应用需要的自定义登录页面

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

获取认证用户信息

图片验证码

生成图片验证码

  1. 根据随机数生成图片
  2. 将随机数存到Session中
  3. 在将生成的图片写到接口的响应中

图片验证码重构

验证码基本参数可配置

验证码图片的宽,高,字符数,失效时间可配置(注意字符数和失效时间不要在请求级配置中)。请求级配置就是在请求验证码时/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(); // 暂时将防护跨站请求伪造的功能置为不可用     } } 

源码解析

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!