当前位置:   article > 正文

第一篇 全网SpringSecurity最详细教程_using generated security password

using generated security password

引入SpringSecurity依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>

 启动SpringBoot项目后

控制台 会打印

Using generated security password: 30abfb1f-36e1-446a-a79b-f70024f589ab

这就是 Spring Security 为默认用户 user 生成的临时密码,是一个 UUID 字符串。

接下来我们去访问任何接口,就可以看到自动重定向到登录页面了:

 在 Spring Security 中,默认的登录页面和登录接口,都是 /login ,只不过一个是 get 请求(登录页面),另一个是 post 请求(登录接口)。

密码为什么说是一个UUID呢?

在 自动化配置类 UserDetailsServiceAutoConfiguration 类中

  1. private String getOrDeducePassword(User user, PasswordEncoder encoder) {
  2. String password = user.getPassword();
  3. if (user.isPasswordGenerated()) {
  4. logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
  5. }
  6. return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
  7. }

 控制台中UUID密码的打印条件为 user.isPasswordGenerated() 为true 

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package org.springframework.boot.autoconfigure.security;
  6. import java.util.ArrayList;
  7. import java.util.Arrays;
  8. import java.util.HashSet;
  9. import java.util.List;
  10. import java.util.Set;
  11. import java.util.UUID;
  12. import org.springframework.boot.context.properties.ConfigurationProperties;
  13. import org.springframework.boot.web.servlet.DispatcherType;
  14. import org.springframework.util.StringUtils;
  15. @ConfigurationProperties(
  16. prefix = "spring.security"
  17. )
  18. public class SecurityProperties {
  19. public static final int BASIC_AUTH_ORDER = 2147483642;
  20. public static final int IGNORED_ORDER = -2147483648;
  21. public static final int DEFAULT_FILTER_ORDER = -100;
  22. private final SecurityProperties.Filter filter = new SecurityProperties.Filter();
  23. private final SecurityProperties.User user = new SecurityProperties.User();
  24. public SecurityProperties() {
  25. }
  26. public SecurityProperties.User getUser() {
  27. return this.user;
  28. }
  29. public SecurityProperties.Filter getFilter() {
  30. return this.filter;
  31. }
  32. public static class User {
  33. private String name = "user";
  34. private String password = UUID.randomUUID().toString();
  35. private List<String> roles = new ArrayList();
  36. private boolean passwordGenerated = true;
  37. public User() {
  38. }
  39. public String getName() {
  40. return this.name;
  41. }
  42. public void setName(String name) {
  43. this.name = name;
  44. }
  45. public String getPassword() {
  46. return this.password;
  47. }
  48. public void setPassword(String password) {
  49. if (StringUtils.hasLength(password)) {
  50. this.passwordGenerated = false;
  51. this.password = password;
  52. }
  53. }
  54. public List<String> getRoles() {
  55. return this.roles;
  56. }
  57. public void setRoles(List<String> roles) {
  58. this.roles = new ArrayList(roles);
  59. }
  60. public boolean isPasswordGenerated() {
  61. return this.passwordGenerated;
  62. }
  63. }
  64. public static class Filter {
  65. private int order = -100;
  66. private Set<DispatcherType> dispatcherTypes;
  67. public Filter() {
  68. this.dispatcherTypes = new HashSet(Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
  69. }
  70. public int getOrder() {
  71. return this.order;
  72. }
  73. public void setOrder(int order) {
  74. this.order = order;
  75. }
  76. public Set<DispatcherType> getDispatcherTypes() {
  77. return this.dispatcherTypes;
  78. }
  79. public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) {
  80. this.dispatcherTypes = dispatcherTypes;
  81. }
  82. }
  83. }

 从 SecurityProperties 中我们可以看出默认用户名为user ,密码为每次启动都不一致的UUID。 同时 passwordGenerated = true 说明密码默认是在控制台打印的。


由于每次启动我们都要有一个新的 UUID密码 因此需要我们自己进行用户名密码配置,去覆盖默认配置。两种方法如下。

第一种方法 :下面是配置的两个步骤。

 第一步 创建 SecurityProperties

  1. /**
  2. * @author ShawnWang
  3. * @create 2021-07-05 20:38
  4. *
  5. * 进行Spring Boot配置文件部署时,发出警告Spring Boot Configuration Annotation Processor not configured,但是不影响运行。
  6. *
  7. * 解决办法 引入下面依赖
  8. * <dependency>
  9. * <groupId>org.springframework.boot</groupId>
  10. * <artifactId>spring-boot-configuration-processor</artifactId>
  11. * <optional>true</optional>
  12. * </dependency>
  13. *
  14. */
  15. @ConfigurationProperties(prefix = "spring.security")
  16. public class SecurityProperties {
  17. }

第二步  在 application.yml 中配置

  1. spring:
  2. security:
  3. user:
  4. name: shawnwang
  5. password: 123

在 yml 中定义的用户名密码最终是通过 set 方法注入到属性中。  重启项目即可使用自定义用户名和密码进行授权登录了。

 

第二种方法:

在配置类中配置,我们就要指定 PasswordEncoder 。

Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 SecureRandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。

不同于 Shiro 中需要自己处理密码加盐,在 Spring Security 中,BCryptPasswordEncoder 就自带了盐,处理起来非常方便。

而 BCryptPasswordEncoder 就是 PasswordEncoder 接口的实现类。

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package org.springframework.security.crypto.password;
  6. public interface PasswordEncoder {
  7. String encode(CharSequence var1);
  8. boolean matches(CharSequence var1, String var2);
  9. default boolean upgradeEncoding(String encodedPassword) {
  10. return false;
  11. }
  12. }
  1. encode 方法用来对明文密码进行加密,返回加密之后的密文。
  2. matches 方法是一个密码校对方法,在用户登录的时候,将用户传来的明文密码和数据库中保存的密文密码作为参数,传入到这个方法中去,根据返回的 Boolean 值判断用户密码是否输入正确。
  3. upgradeEncoding 是否还要进行再次加密,这个一般来说就不用了。

具体配置

创建 SecurityConfig 继承 WebSecurityConfigurerAdapter 重写里边的 configure 方法。

  1. /**
  2. * @author ShawnWang
  3. * @create 2021-07-06 8:59
  4. */
  5. @Configuration
  6. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  7. @Bean
  8. PasswordEncoder passwordEncoder(){
  9. // return NoOpPasswordEncoder.getInstance();
  10. return new BCryptPasswordEncoder();
  11. }
  12. @Override
  13. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  14. auth.inMemoryAuthentication()
  15. .withUser("shawn")
  16. .password("123").roles("admin");
  17. }
  18. @Override
  19. public void configure(WebSecurity web) {
  20. //用来配置忽略的url地址 一般为静态文件
  21. web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
  22. }
  23. @Override
  24. protected void configure(HttpSecurity http) throws Exception {
  25. http.authorizeRequests()
  26. .anyRequest().authenticated()
  27. .and()
  28. .formLogin()
  29. //自定义登录页 不用自带的登录页
  30. .loginPage("/login.html")
  31. //指定登录接口地址
  32. .loginProcessingUrl("/doLogin")
  33. //和successForwardUrl 两者只能存在其一 .defaultSuccessUrl("/index",true)同.successForwardUrl("/index")作用一致
  34. //两种情况 一、你在浏览器输入登录地址 登录成功后跳转到/index,输入其他如http://localhost/hello 登录成功后就跳转到/hello
  35. .defaultSuccessUrl("/index")
  36. //successForwardUrl 无论输入什么地址 登陆成功后都会跳转到/index
  37. .successForwardUrl("/index")
  38. //表示登录相关的页面/接口不要被拦截
  39. .permitAll()
  40. .and()
  41. //默认注销登录URL是/logout 可以通过logoutUrl方法来修改默认的注销URL
  42. .logout()
  43. .logoutUrl("/logout2")
  44. .and()
  45. .csrf().disable();
  46. }
  47. }
  1. 首先我们提供了一个 PasswordEncoder 的实例,因为目前的案例还比较简单,因此我暂时先不给密码进行加密,所以返回 NoOpPasswordEncoder 的实例即可。
  2. configure 方法中,我们通过 inMemoryAuthentication 来开启在内存中定义用户,withUser 中是用户名,password 中则是用户密码,roles 中是用户角色。
  3. 如果需要配置多个用户,用 and 相连。 (因为现在的 and 符号相当于就是 XML 标签的结束符,表示结束当前标签)

 自定义表单登录页

 重写它的 configure(WebSecurity web) 和 configure(HttpSecurity http) 方法 代码段在上面

  1. web.ignoring() 用来配置忽略掉的 URL 地址,一般对于静态文件,我们可以采用此操作。
  2. 如果我们使用 XML 来配置 Spring Security ,里边会有一个重要的标签 <http>,HttpSecurity 提供的配置方法 都对应了该标签。
  3. authorizeRequests 对应了 <intercept-url>
  4. formLogin 对应了 <formlogin>
  5. and 方法表示结束当前标签,上下文回到HttpSecurity,开启新一轮的配置。
  6. permitAll 表示登录相关的页面/接口不要被拦截。
  7. 最后记得关闭 csrf  (在Security的默认拦截器里,默认会开启CSRF处理,判断请求是否携带了token,如果没有就拒绝访问)

 只需将login.html放到 resources -> static 下即可

html中

<!-- action="/doLogin" 时请求自定义的接口  为/login.html时为自定义的页面 -->
<!--    <form action="/doLogin" method="post">-->

登录回调

在登录成功之后,我们就要分情况处理了,大体上来说,无非就是分为两种情况:

  • 前后端分离登录
  • 前后端不分登录

两种情况的处理方式不一样。

(前后端不分登录 如下)

3.1 登录成功回调

在 Spring Security 中,和登录成功重定向 URL 相关的方法有两个:

  • defaultSuccessUrl
  • successForwardUrl

这两个咋看没什么区别,实际上内藏乾坤。

首先我们在配置的时候,defaultSuccessUrl 和 successForwardUrl 只需要配置一个即可,具体配置哪个,则要看你的需求,两个的区别如下:

  1. defaultSuccessUrl 有一个重载的方法,我们先说一个参数的 defaultSuccessUrl 方法。如果我们在 defaultSuccessUrl 中指定登录成功的跳转页面为 /index,此时分两种情况,如果你是直接在浏览器中输入的登录地址,登录成功后,就直接跳转到 /index,如果你是在浏览器中输入了其他地址,例如 http://xxxx/hello,结果因为没有登录,又重定向到登录页面,此时登录成功后,就不会来到 /index ,而是来到 /hello 页面。
  2. defaultSuccessUrl 还有一个重载的方法,第二个参数如果不设置默认为 false,也就是我们上面的的情况,如果手动设置第二个参数为 true,则 defaultSuccessUrl 的效果和 successForwardUrl 一致。
  3. successForwardUrl 表示不管你是从哪里来的,登录后一律跳转到 successForwardUrl 指定的地址。例如 successForwardUrl 指定的地址为 /index ,你在浏览器地址栏输入 http://xxxx/hello,结果因为没有登录,重定向到登录页面,当你登录成功之后,就会服务端跳转到 /index 页面;或者你直接就在浏览器输入了登录页面地址,登录成功后也是来到 /index

3.2 登录失败回调

与登录成功相似,登录失败也是有两个方法:

  • failureForwardUrl
  • failureUrl

这两个方法在设置的时候也是设置一个即可。failureForwardUrl 是登录失败之后会发生服务端跳转,failureUrl 则在登录失败之后,会发生重定向。

  1. .and()
  2. //默认注销登录URL是/logout 可以通过logoutUrl方法来修改默认的注销URL
  3. .logout()
  4. .logoutUrl("/logout2")
  5. .logoutRequestMatcher(new AntPathRequestMatcher("/logout2","POST"))
  6. .logoutSuccessUrl("/successIndex")
  7. .deleteCookies()
  8. .clearAuthentication(true)
  9. .invalidateHttpSession(true)
  10. .permitAll()
  11. .and()

注销登录的配置:

  1. 默认注销的 URL 是/logout2,是一个 GET 请求,我们可以通过 logoutUrl 方法来修改默认的注销 URL。
  2. logoutRequestMatcher 方法不仅可以修改注销 URL,还可以修改请求方式,实际项目中,这个方法和 logoutUrl 任意设置一个即可。
  3. logoutSuccessUrl 表示注销成功后要跳转的页面。
  4. deleteCookies 用来清除 cookie。
  5. clearAuthentication 和 invalidateHttpSession 分别表示清除认证信息和使 HttpSession 失效,默认可以不用配置,默认就会清除。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/262941
推荐阅读
相关标签
  

闽ICP备14008679号