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

源码解析

原文地址:https://www.cnblogs.com/okokabcd/p/9770342.html

时间: 2024-10-01 02:17:07

Spring Security之用户名+密码登录的相关文章

Spring Security 实现手机验证码登录

思路:参考用户名密码登录过滤器链,重写认证和授权 示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出): 由于涉及内容较多,建议先复制到本地工程中,然后在细细研究. 1.   新建Maven项目  sms-code-validate 2.   pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchem

关于spring-data-mongodb用户名密码登录报错问题:Failed to authenticate to database

一.问题 1.spring-data-mongodb用户名密码登录报错问题:Failed to authenticate to database  org.springframework.data.mongodb.CannotGetMongoDbConnectionException: Failed to authenticate to database [ashop], username = [ashop], password = [g***********4] org.springframe

使用openssh实现免密码登录,免用户名+密码登录

有时候经常登录某台主机,会懒得输入密码,输入个用户名登录,像我这种更懒的,密码也懒得输入: 创建虚拟机:centos7 HuaiqingdeMBP:~ huaiqingcheng$ vagrant box add centos7 /Users/huaiqingcheng/Downloads/centos-7.0-x86_64.box ==> box: Box file was not detected as metadata. Adding it directly... ==> box: Ad

spring security中配置密码为md5的带salt加密

spring security中配置密码为md5的带salt加密 service: private Md5PasswordEncoder encoder; //spring security md5 public Md5PasswordEncoder getEncoder() { return encoder; } @Resource public void setEncoder(Md5PasswordEncoder encoder) { this.encoder = encoder; } @O

Spring Security OAuth2 Demo —— 密码模式(Password)

前情回顾 前几节分享了OAuth2的流程与授权码模式和隐式授权模式两种的Demo,我们了解到授权码模式是OAuth2四种模式流程最复杂模式,复杂程度由大至小:授权码模式 > 隐式授权模式 > 密码模式 > 客户端模式 其中密码模式的流程是:让用户填写表单提交到授权服务器,表单中包含用户的用户名.密码.客户端的id和密钥的加密串,授权服务器先解析并校验客户端信息,然后校验用户信息,完全通过返回access_token,否则默认都是401 http状态码,提示未授权无法访问 本文目标 编写与

zabbix3.0 监控mysql服务免用户名密码登录的问题故障处理详细过程

1,My.cnf中用户名密码无效 在azure云上面,使用Zabbix监控mysql中,发现在/usr/local/mysql/my.cnf里面设置的默认用户名密码无效,出不来数据,而且在zabbix服务器上,使用zabbix_get也报错failed,如下 [[email protected]_serv_121_12 ~]#/usr/local/zabbix/bin/zabbix_get -s 192.168.13.13 -p10050 -kmysql.status[Uptime] /usr/

安装openvpn并使用证书+用户名密码登录

openvpn是一个vpn工具,用于创建虚拟专用网络(Virtual Private Network)加密通道的免费开源软件,提供证书验证功能,也支持用户名密码认证登录方式,当然也支持两者合一,为服务器登录和连接提供更加安全的方式,可以在不同网络访问场所之间搭建类似于局域网的专用网络通道,配合特定的代理服务器,可用于访问特定受限网站(你懂得)或者突破内部网络限制. 安装 模拟运行环境:centos6系列系统 # 关闭selinux setenforce 0 sed -i '/^SELINUX=/

Spring Security 实战:QQ登录实现

准备工作 1.在 QQ互联 申请成为开发者,并创建应用,得到APP ID 和 APP Key.2.了解QQ登录时的 网站应用接入流程.(必须看完看懂) 为了方便各位测试,直接把我自己申请的贡献出来:APP ID : 101386962APP Key:2a0f820407df400b84a854d054be8b6a回调地址:http://www.ictgu.cn/login/qq 提醒:因为回调地址不是 http://localhost ,所以在启动我提供的demo时,需要在host文件中添加一行

spring security 5.0 密码未加密报错

使用spring security5.0后,配置文件中直接写普通的密码如:123456,会报错: java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" 这是因为spring security5.0以后默认需要选择密码加密方式,如果还像之前版本直接配置未加密密码,就会报上面这个错误当然啦,如果还想用简单密码的话,spring security还是给了两个方案,一种是