1.pom新增依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </dependency>

添加一个接口用来测试

  1. @RestController
  2. public class SecurityDemoController {
  3. @RequestMapping("/security")
  4. public String securityUserPassword(){
  5. return "security req success!!!";
  6. }
  7. }

启动spring boot 服务,控制台会有一行打印日志,密码为一串 UUID。登录页面弹窗输入默认用户 user 和密码
image.png
image.png

2.用户配置

2.1 配置文件配置用户

image.png
可以通过配置文件中配置修改默认的用户名与密码:
image.png
image.png

2.2 配置类配置用户

配置类会覆盖配置文件中的用户信息

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Bean
  4. PasswordEncoder setPasswordEncoder(){
  5. // 不加密(可自己实现 PasswordEncoder ,注入)
  6. return NoOpPasswordEncoder.getInstance();
  7. }
  8. @Override
  9. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  10. auth.inMemoryAuthentication().withUser("shf").password("123456").roles("admin")
  11. .and().withUser("afei").password("123").roles("u");
  12. }
  13. }

image.png

2.3 自定义登录页面配置,web资源拦截规则等

配置 web 拦截规则
image.png
配置登录页面
image.png
此时,启动项目,访问任意接口,返回自定义的登录页面上。
若不配置此处,默认为:
image.png

2.4 自定义登录参数(默认 username/password)

image.png

2.5 登录回调(前后端不分离情况)

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

  • defaultSuccessUrl
  • successForwardUrl

这两个咋看没什么区别,实际上内藏乾坤。
首先我们在配置的时候,defaultSuccessUrl 和 successForwardUrl 只需要配置一个即可,具体配置哪个,则要看你的需求,两个的区别如下:

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

相关配置如下:

  1. .and()
  2. .formLogin()
  3. .loginPage("/login.html")
  4. .loginProcessingUrl("/doLogin")
  5. .usernameParameter("name")
  6. .passwordParameter("passwd")
  7. .defaultSuccessUrl("/index")
  8. .successForwardUrl("/index")
  9. .permitAll()
  10. .and()

「注意:实际操作中,defaultSuccessUrl 和 successForwardUrl 只需要配置一个即可。」

2.6 登录失败回调(前后端不分离情况)

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

  • failureForwardUrl
  • failureUrl

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

2.7 注销回调(前后端不分离情况)

注销登录的默认接口是 /logout,我们也可以配置。

  1. .and()
  2. .logout()
  3. .logoutUrl("/logout")
  4. .logoutRequestMatcher(new AntPathRequestMatcher("/logout","POST"))
  5. .logoutSuccessUrl("/index")
  6. .deleteCookies()
  7. .clearAuthentication(true)
  8. .invalidateHttpSession(true)
  9. .permitAll()
  10. .and()

注销登录的配置:

  1. 默认注销的 URL 是 /logout,是一个 GET 请求,我们可以通过 logoutUrl 方法来修改默认的注销 URL。
  2. logoutRequestMatcher 方法不仅可以修改注销 URL,还可以修改请求方式,实际项目中,这个方法和 logoutUrl 任意设置一个即可。
  3. logoutSuccessUrl 表示注销成功后要跳转的页面。
  4. deleteCookies 用来清除 cookie。
  5. clearAuthentication 和 invalidateHttpSession 分别表示清除认证信息和使 HttpSession 失效,默认可以不用配置,默认就会清除。

    3.前后端分离

    3.1 有状态登录

    1. 有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:<br />服务端保存大量数据,增加服务端压力<br />服务端保存用户状态,不支持集群化部署

    3.2 无状态登录

    1. 微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即:
    • 服务端不保存任何客户端请求者信息
    • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

那么这种无状态性有哪些好处呢?

  • 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)
  • 减小服务端存储压力

    3.3 前后分离登录成功

    image.png
    successHandler 方法的参数是一个 AuthenticationSuccessHandler 对象,这个对象中我们要实现的方法是 onAuthenticationSuccess。
    onAuthenticationSuccess 方法有三个参数,分别是:

  • HttpServletRequest

  • HttpServletResponse
  • Authentication

有了前两个参数,我们就可以在这里随心所欲的返回数据了。利用 HttpServletRequest 我们可以做服务端跳转,利用 HttpServletResponse 我们可以做客户端跳转,当然,也可以返回 JSON 数据。
第三个 Authentication 参数则保存了我们刚刚登录成功的用户信息
image.png

3.4前后端分离登录失败

配置失败回调
image.png
image.png

3.5 前后分离,未认证情况不走重定向,由前端决定跳转(配置后不跳转到登录页)

image.png
3.6 注销
image.png
image.png
-

  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3. http.authorizeRequests() // authorizeRequests 拦截规则,配置请求 url
  4. .anyRequest().authenticated()
  5. .and() // 连接符,多项配置之间 and 链接
  6. .formLogin() // formLogin 配置登录资源
  7. // 前后端分离登录配置,登录成功返回
  8. .successHandler((req, resp, authentication) -> {
  9. Object principal = authentication.getPrincipal();
  10. resp.setContentType("application/json;charset=utf-8");
  11. PrintWriter out = resp.getWriter();
  12. out.write(new ObjectMapper().writeValueAsString(principal));
  13. out.flush();
  14. out.close();
  15. })
  16. // e Exception 信息
  17. .failureHandler((req, resp, e) -> {
  18. resp.setContentType("application/json;charset=utf-8");
  19. PrintWriter out = resp.getWriter();
  20. out.write("登录失败:"+e.getMessage());
  21. out.flush();
  22. out.close();
  23. })
  24. .and()
  25. .logout()
  26. .logoutUrl("/logout") // 注销接口
  27. .logoutSuccessHandler((req, resp, authentication) -> {
  28. resp.setContentType("application/json;charset=utf-8");
  29. PrintWriter out = resp.getWriter();
  30. out.write("注销成功");
  31. out.flush();
  32. out.close();
  33. })
  34. .permitAll()
  35. .and()
  36. .csrf().disable().exceptionHandling() // 关闭防 CSRF 攻击
  37. // 配置重定向策略,由前端决定
  38. .authenticationEntryPoint((req, resp, authException) -> {
  39. resp.setContentType("application/json;charset=utf-8");
  40. PrintWriter out = resp.getWriter();
  41. out.write("尚未登录,请先登录");
  42. out.flush();
  43. out.close();
  44. }
  45. );
  46. }

4. 权限配置

4.1 配置接口权限

image.png

  1. http.authorizeRequests() // authorizeRequests 拦截规则,配置请求 url
  2. .antMatchers("/admin/**").hasRole("admin") // 接口 admin 的需要 admin 角色
  3. .antMatchers("/user/**").hasRole("user")
  4. // 注意:anyRequest 必须在 antMatchers 之后
  5. .anyRequest().authenticated()

4.2 配置角色继承关系

  1. @Bean
  2. RoleHierarchy roleHierarchy() {
  3. RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
  4. // 角色继承,配置 admin 角色可访问 user 角色资源 / 规则 ROLE_ 前缀+角色
  5. hierarchy.setHierarchy("ROLE_admin > ROLE_user");
  6. return hierarchy;
  7. }

5.配置密码加解密规则

自定义实现 PasswordEncoder

  1. package com.camelot.security.config;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.security.crypto.password.PasswordEncoder;
  4. /**
  5. * <p>
  6. * Description:[]
  7. * </p>
  8. *
  9. * @author shf
  10. * @version 1.0
  11. * @date Created on 2020/4/27 18:07
  12. */
  13. public class MyPasswordEncoder implements PasswordEncoder {
  14. @Override
  15. public String encode(CharSequence rawPassword) {
  16. try {
  17. // 加密
  18. String s = AESUtil.aesEncrypt(rawPassword.toString(), "cBssbHB3ZA==HKXT");
  19. return s;
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. return null;
  24. }
  25. @Override
  26. public boolean matches(CharSequence rawPassword, String encodedPassword) {
  27. // 密码校验
  28. if (rawPassword.equals(encodedPassword)) {
  29. return true;
  30. }
  31. return false;
  32. }
  33. }

配置实现 setPasswordEncoder 方法,注入加密类

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Bean
  4. PasswordEncoder setPasswordEncoder(){
  5. // 不加密
  6. return new MyPasswordEncoder();
  7. }