当前位置:   article > 正文

SpringSecuriyt_loaduserbyusername

loaduserbyusername

SpringSecurity默认的登录逻辑如下:

1、认证 -- 也就是登录的大致流程:

传统的登录,也就是通过form表单登录,使用sequrity做安全框架,当一个用户点击登录,首先security会去找 userDetailsServese 类里边的 loadUserByUsername 方法 -> 参数传递用户名 -> 拿着用户名,去数据库中查找,如果用户名存在 -> 会返回 UserDetails,而UserDetails 是一个接口,对应实现类是User,User类中的参数有用户名、密码、权限,也就是说返回的有用户名、密码和权限,接着会对前端传过来的密码和数据库返回的密码进行比对。

登录逻辑

内部通过  loadUserByUsername 接口 进行登录逻辑,

参数:就是登陆时传递的username账号

异常:用户名未找到

 返回类型

返回的类型为  UserDetails 接口

  UserDetails 接口的三个实现类

默认有三个实现了UserDetails 接口

User类

 平时开发主要用到: User类,注意与自定义的User不能混淆。

密码处理 -  PasswordEncoder接口

encode 方法,对前端传过来的密码进行加密;参数为前端传过来的密码。

matches 数据库的密码和前端传过来的密码进行匹配,参数一:前端传过来的密码,参数二:查询出来的密码;

PasswordEncoder接口的实现类

默认有非常多的实现类。

常用的  BCryptPasswordEncoder 实现类,也是官方推荐的一个。

 有三个常量

  1. private final int strength; //密码的加密强度 默认为10
  2. private final BCryptVersion version;
  3. private final SecureRandom random;

 对应encode方法

大致流程

首先判断前端传过来的密码是否为空,是的话直接抛异常。有密码,接着生成盐,判断是否有随机数,没有的话使用版本号、加密强度生成盐;有的话,使用 版本号、加密强度、随机数生成盐,最后,根据哈希算法,对密码+盐进行加密,通过 strength 加密强度进行加密,加密强度默认为10。

 

测试

搞懂上边的大致流程,我们来用一个密码简单测试下,看下生成的密码和密码比对

  1. @Test
  2. void contextLoads() {
  3. BCryptPasswordEncoder bp = new BCryptPasswordEncoder();
  4. String psw = bp.encode("123");
  5. System.out.println("加密后的密码:"+ psw);
  6. }

 下边就是对前端过传来的密码进行加密之后的样子:

对密码"123"多次执行,生成出来的加密密码也是不一样的,因为内部生成的盐每一次都是随机生成!

  查询出来的密码如何与上边加密后的密码进行比对呢, 可以使用 matches() 方法:

=======================================================

言归正传,记录下在springboot中的使用。

1、简单实现自己的登陆逻辑

如果不自己实现,默认访问应用,会弹出一个security默认的一个登录页面,默认的账号和密码是security提供的,账号admin,密码是每次启动应用随机生成的,下边写一个自己的登录逻辑:

因为要模拟密码加密,所以加一个PasswordEncoder的 Bean,下边创建了一个Security的配置类:

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  4. import org.springframework.security.crypto.password.PasswordEncoder;
  5. @Configuration
  6. public class SecurityConfig {
  7. @Bean
  8. public PasswordEncoder passwordEncoder() {
  9. return new BCryptPasswordEncoder();
  10. }
  11. }

 实现自己的登陆逻辑主要是要实现UserDetailsService接口:

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.security.core.authority.AuthorityUtils;
  3. import org.springframework.security.core.userdetails.User;
  4. import org.springframework.security.core.userdetails.UserDetails;
  5. import org.springframework.security.core.userdetails.UserDetailsService;
  6. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  7. import org.springframework.security.crypto.password.PasswordEncoder;
  8. import org.springframework.stereotype.Service;
  9. @Service
  10. public class UserDetailServiceImpl implements UserDetailsService {
  11. @Autowired
  12. PasswordEncoder passwordEncoder;
  13. @Override
  14. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  15. System.out.println(username);
  16. // 账号admin,密码:123
  17. // 仿数据库
  18. if(!username.equals("admin")) {
  19. throw new UsernameNotFoundException("用户名不存在");
  20. }
  21. // 比较密码,这里仿一个密码加密
  22. String password = passwordEncoder.encode("123");
  23. // 账号、密码、权限
  24. return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
  25. }
  26. }

2、实现自己的登录页面,默认security自带的登录页,实现自己的如下:

继承

WebSecurityConfigurerAdapter ,重写 configure方法
  1. import org.springframework.context.annotation.Configuration;
  2. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  3. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  4. @Configuration
  5. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  6. // ··· ···
  7. @Override
  8. protected void configure(HttpSecurity http) throws Exception {
  9. // 表单的登录
  10. http.formLogin()
  11. .loginPage("/login.html"); // 自己的登录页面(在static中的html),注意前边加/
  12. }
  13. }

3、上边我们自定义了:登录页面和登陆逻辑,此时,在浏览器访问任何api接口或应用都能访问,显然不是我们想要的,因为没有相应的权限了,没有登录,直接就能访问任何页面???

解决:

请求授权,任何请求都需要授权,接着重启服务,访问任何页面,此时浏览器崩了:

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. // ··· ···
  4. @Override
  5. protected void configure(HttpSecurity http) throws Exception {
  6. // 表单的登录
  7. http.formLogin()
  8. .loginPage("/login.html"); // 登录页面
  9. // 请求授权
  10. http.authorizeRequests()
  11. .anyRequest().authenticated(); // 任何请求都需要授权
  12. }
  13. }

 原因:任何请求都需要授权,它会重定向到登录页面,但是登录页面也需要授权,所以死循环。

 解决:添加一组放行的路径,规定登录页面不需要授权。

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. // 表单的登录
  6. http.formLogin()
  7. .loginPage("/login.html"); // 登录页面
  8. // 请求授权
  9. http.authorizeRequests()
  10. // 匹配一个可以放行的路径
  11. .mvcMatchers("/login.html").permitAll()
  12. .anyRequest().authenticated(); // 任何请求都需要授权
  13. }
  14. }

4、到了我们自定义的登录页面之后,我们想要登录后直接访问之前写好的登陆成功页面:

下边是我们写好的自定义登录页面:,点击登录会请求/loigin接口,

 login接口会被下边loginProcessingUrl() 方法匹配到,然后会跳转至 /toSuccess api,这个api必须是post请求的,因为successForwardUlr() 方法只接收post请求,而且该方法的参数必须是一个控制器中定义的api接口,且控制器中返回的是一个视图名,原因在最后源码中有说明,内部只是一个简单个跳转!!!

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. // 表单的登录
  6. http.formLogin()
  7. .loginPage("/login.html") // 登录页面
  8. // 登录要处理的url
  9. .loginProcessingUrl("/login") // 这个路径需要跟表单提交的登录路径相同,才能走我们自定义的登陆逻辑
  10. // 登陆成功后跳转的页面(只接受post请求)
  11. .successForwardUrl("/toSuccess");
  12. // 关闭防火墙(关闭csrf 跨站请求伪造)
  13. http.csrf().disable();
  14. // 请求授权
  15. http.authorizeRequests()
  16. // 匹配一个可以放行的路径
  17. .mvcMatchers("/login.html").permitAll()
  18. .anyRequest().authenticated(); // 任何请求都需要授权
  19. }
  20. }

 到控制器下的toSuccess接口,里边会重定向到success.html页面,完成一次从   登录自定义逻辑 -> 到我们自定义登录页面 -> 授权 -> 登陆成功 的请求。

 

5、登录失败失败的处理。

上边登录成功大致流程跑通了,但是如果登录失败呢,需要添加一个失败的页面:

配置ssecurity:

(1)failureForwardUrl() 失败的跳转的api。
(2)mvcMatchers("error.html") 需要添加要放行的页面,否则到不了失败页面!

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Bean
  4. public PasswordEncoder passwordEncoder() {
  5. return new BCryptPasswordEncoder();
  6. }
  7. @Override
  8. protected void configure(HttpSecurity http) throws Exception {
  9. // 表单的登录
  10. http.formLogin()
  11. .loginPage("/login.html") // 登录页面
  12. // 登录要处理的url
  13. .loginProcessingUrl("/login") // 这个路径需要跟表单提交的登录路径相同,才能走我们自定义的登陆逻辑
  14. // 登陆成功后跳转的页面(只接受post请求)
  15. .successForwardUrl("/toSuccess")
  16. // 登陆失败后跳转的页面(只接受post请求)
  17. .failureForwardUrl("/toError");
  18. // 关闭防火墙(关闭csrf 跨站请求伪造)
  19. http.csrf().disable();
  20. // 请求授权
  21. http.authorizeRequests()
  22. // 匹配一个可以放行的路径
  23. .mvcMatchers("/login.html", "/error.html").permitAll()
  24. .anyRequest().authenticated(); // 任何请求都需要授权
  25. }
  26. }

 账号密码输错了:

 会转到失败页面:

 6、自定义的请求参数名

上边我们自定义的登录页面,请求参数名是:username和password,请求方法是:post,这是security默认的,必须这么写,。 

自定义参数名:

  配置:

 7、自定义页面跳转

  1. 自定义成功处理器:
  2. successHandler(new PageSuccessHandler("http://www.baidu.com"))

 登录成功之后的页面跳转,上边是通过一个api接口,转到控制层,在由控制器里边重定向到一个html页面,但是,现在前后端分离项目,如果想直接跳转到百度页面,该如何做?

 successForwardUrl("http://www.baidu.com"),此时单纯这样写无法完成此需求!

 看下successForwardUrl源码:

 解决:

实现我们自定义的跳转,其实也很简单,实现 AuthenticationSuccessHandler 接口,重写:onAuthenticationSuccess方法即可。

  1. import org.springframework.security.core.Authentication;
  2. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. public class PageSuccessHandler implements AuthenticationSuccessHandler {
  8. private final String url;
  9. public PageSuccessHandler(String url) {
  10. this.url = url;
  11. }
  12. @Override
  13. public void onAuthenticationSuccess(
  14. HttpServletRequest httpServletRequest,
  15. HttpServletResponse httpServletResponse,
  16. Authentication authentication) throws IOException, ServletException {
  17. // 重定向到url地址
  18. httpServletResponse.sendRedirect(this.url);
  19. }
  20. }

修改security配置:

 

 8、来看下Authentication

下边接口在实际开发中用的很多。

onAuthenticationSuccess 方法的第三个参数 :Authentication

  1. public class PageSuccessHandler implements AuthenticationSuccessHandler {
  2. private final String url;
  3. public PageSuccessHandler(String url) {
  4. this.url = url;
  5. }
  6. @Override
  7. public void onAuthenticationSuccess(
  8. HttpServletRequest httpServletRequest,
  9. HttpServletResponse httpServletResponse,
  10. Authentication authentication) throws IOException, ServletException {
  11. // 获取当前登录的这个用户
  12. User user = (User) authentication.getPrincipal();
  13. System.out.println(user.getUsername()); // 获取登录之后的用户账号
  14. System.out.println(user.getPassword()); // 获取登陆之后的密码,为了安全security默认为null
  15. System.out.println(user.getAuthorities()); // 获取当前用户的权限
  16. httpServletResponse.sendRedirect(this.url);
  17. }
  18. }

 最后的权限是之前我们设置的:

9、定义登录失败的处理器

先来看下之前登录失败的处理方式,通过 failureForwardUrl(url)这个url必须对应的是控制器中的返回的视图,如果直接写一个xxx.html是不可行的!!!源码跟登陆成功的处理方式一样,只不过最后 onAuthenticationFailure() 授权失败的处理方法参数不一样:

 实现自定义登录失败的处理(跟成功的类似):
实现 AuthenticationFailureHandler 接口,重写 onAuthenticationFailure() 方法

  1. package com.lxc.sequrity.config;
  2. import org.springframework.security.core.AuthenticationException;
  3. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. public class PageFailHandler implements AuthenticationFailureHandler {
  9. private final String url;
  10. public PageFailHandler(String url) {
  11. this.url = url;
  12. }
  13. @Override
  14. public void onAuthenticationFailure(
  15. HttpServletRequest httpServletRequest,
  16. HttpServletResponse httpServletResponse,
  17. AuthenticationException e) throws IOException, ServletException {
  18. httpServletResponse.sendRedirect(this.url);
  19. }
  20. }

最后修改配置:

 登陆失败,跳转error.html成功

 

 总结:

不管登录成功的处理逻辑或登录失败的处理逻辑,其实都是需要实现成功认证的处理接口和失败的认证处理接口,重写里边的方法,在方法中定义我们的登录逻辑。其次,在配置类中 调用 :

successHandler(new 自定义的处理类) 或 failureHandler(new 自定义的处理类) 处理方法 

补充:

BCryptPasswordEncoder 和、passwordEncoder两个密码类,security内部使用的,对密码进行加密,对比等。

  1. @Configuration
  2. public class SecurityConfig {
  3. @Bean
  4. public BCryptPasswordEncoder bCryptPasswordEncoder () {
  5. return new BCryptPasswordEncoder();
  6. }
  7. }

(2)我们自己实现登录逻辑,所以,必须要实现 userDetailsServese () 接口

  1. @Service
  2. public class UserDetailsServiceImpl implements UserDetailsService {
  3. @Resource
  4. private PasswordEncoder passwordEncoder;
  5. @Override
  6. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  7. System.out.println("username:"+username);
  8. // 【第一步】
  9. // 参数:username:前端传过来的账号,拿username直接去数据库查询,如果username为null 直接抛出异常 :UsernameNotFoundException
  10. // 为了方便这里直接,写死
  11. String databaseUsername = "admin";
  12. if(databaseUsername.equals(username)) {
  13. throw new UsernameNotFoundException("用户名不存在");
  14. }
  15. // 【第二步】
  16. // 密码比对
  17. // 我们在这拿到前端传过来的密码,springSequrity 已经帮我们加密了,此时,在这里我们只是把加密后的
  18. // 密码和数据库查询到的密码做比对即可。
  19. // 这里模拟加密,实际开发中,注册时就已经加密了,应该使用matchs()进行比对,成功返回 UserDetails
  20. // 下边应该写 matchers() 方法,进行密码比对,为了方便这里跳过密码比对,直接模
  21. // 拟一个加密后的密码
  22. String password = passwordEncoder.encode("123");
  23. return new User(username,
  24. password,
  25. // AuthorityUtils 权限的工具类
  26. AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
  27. }
  28. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/481580
推荐阅读
相关标签
  

闽ICP备14008679号