zoukankan      html  css  js  c++  java
  • 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(); // 暂时将防护跨站请求伪造的功能置为不可用
        }
    }
    

    问题

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

    源码解析

  • 相关阅读:
    C语言编程题
    boost-使用说明
    CButton控件
    sprintf()与sscanf()
    MFC中的几个虚函数
    CProgressCtrl进度条控件实现进度滚动效果
    移动窗口和根据条件查找指定窗口
    VC播放mp3的方法
    CEdit控件[转]
    关于鼠标的一些操作
  • 原文地址:https://www.cnblogs.com/okokabcd/p/9770342.html
Copyright © 2011-2022 走看看