学习SpringSecurity

使用的环境:SpringBoot:2.6.13,JDK11,SpringSecurity:5.6.8(跟随springboot版本)

目录

入门案例

自定义登录逻辑

自定义登录页面

失败跳转

自定义处理器(适合前后端分离,或者跳转其他页面)

登录成功的:

登录失败的:

配置类中的常用方法详解

权限、角色、IP判断

自定义403处理

自定义实现权限控制(结合access)

使用注解配置

RememberMe - 记住我功能

在Thymeleaf中获取security属性值和判断权限

退出登录

CSRF跨站保护

Oauth2协议

简介

Oauth2四种授权模式

授权码模式演示

密码模式演示

Redis存储Token

JWT - demo

SecurityOauth2整合Jwt

扩展jwt中的存储内容


入门案例

1、maven导坐标

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2、启动springboot项目后访问http://127.0.0.1:8080


所有的请求都要通过SpringSecurity的验证,这个登录验证界面是SpringSecurity自带的,账号是user,密码打印在控制台

自定义登录逻辑

1、编写BeanConfig配置类,添加Bean如下

@Configuration
public class BeanConfig {

    // 用于自定义登录逻辑中的密码加密和验证密码
    @Bean
    public PasswordEncoder passwordEncoder (){
        return new BCryptPasswordEncoder();
    }

}

2、编写UserDetailsServerImpl实现类

package com.zwf.springsecurity.server.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;


@Service
public  class UserDetailsServerImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("执行了UserDetailsServerImpl中的loadUserByUsername方法");

        // 1.查询数据库判断用户是否存在,如果不存在,抛出异常
        if (!"admin".equals(username)){
            throw new UsernameNotFoundException("用户名不存在");
        }

        // 2.把查询出来的密码(注册时已经加密过)进行解析,或者直接把密码放到构造方法中
        String password = passwordEncoder.encode("123");

        // admin表示拥有管理员权限,normal表示拥有普通权限
        return new User(username, password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("" + "admin,normal"));
    }
}

自定义登录页面

1、编写login.html和main.html

login.html

<body>
<form action="/login" method="post">
  用户名 <input type="text" name="username"> <br/>
  密码   <input type="password" name="password"> <br/>
  <input type="submit" value="登录">
</form>
</body>

如果没有配置,那么表单中的 input标签中name属性必须是username和password ,如果想要换成username123个password 123,那么需要在SpringSecurity配置类中配置:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {      

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin()
                .usernameParameter("username")
                .passwordParameter("password");
    }
}

main.html

<body>
<h1>登陆成功</h1>
</body>

2、编写 LoginController 代码

@Controller
public class LoginController {

    // 重定向到登录页面
    @RequestMapping ("/login")
    public String index() {
        return "redirect:main.html";
    }

    // 重定向到登录成功页面
    @RequestMapping ("/toMain")
    public String toMain() {
        return "redirect:main.html";
    }
}

3、编写BeanConfig类

@Configuration
public class BeanConfig {
    // 用于自定义登录逻辑中的密码加密和验证密码
    @Bean
    public PasswordEncoder passwordEncoder (){
        return new BCryptPasswordEncoder();
    }

}

4、编写SpringSecurity配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 自定义登录页面
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin()
                // 自定义登录页面路径
                .loginPage("/login.html")
                // 当发现是login请求时,去执行UserDetailsServerImpl,必须和html表单的请求路径一样
                .loginProcessingUrl("/login")
                // 登录成功后跳转到指定controller路径,必须是post请求
                .successForwardUrl("/toMain");


        // 授权认证
        http.authorizeRequests()
                // 放开登录页面(这个一定要写在前面,不然报错)
                .antMatchers("/login.html").permitAll()
                // 所有请求都需要认证
                .anyRequest().authenticated();

        // 关闭csrf保护,类似防火墙
        http.csrf().disable();
    }
}

失败跳转

1、编写error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>error</title>
</head>
<body>
<h1>登录失败,请重新登录</h1>
<a href="login.html">点击跳转登录</a>
</body>
</html>

2、编写SpringSecurity配置类,添加如下代码

        // 表单提交
        http.formLogin()
                // 自定义登录页面路径
                .loginPage("/login.html")
                // 当发现是login请求时,去执行UserDetailsServerImpl,必须和html表单的请求路径一样
                .loginProcessingUrl("/login")
                // 登录成功后跳转到指定controller路径,必须是post请求
                .successForwardUrl("/toMain")
                // 登录失败后跳转到指定controller路径,必须是post请求
                .failureForwardUrl("/toError");


        // 授权认证
        http.authorizeRequests()
                // 放开login.html和error.html页面(这个一定要写在前面,不然报错)
                .antMatchers("/error.html").permitAll()
                .antMatchers("/login.html").permitAll()
                // 所有请求都需要认证
                .anyRequest().authenticated();

自定义处理器(适合前后端分离,或者跳转其他页面)

登录成功的:

1、编写新的handler代码

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.sendRedirect(url);
    }


}

2、编写配置类,比如跳转到百度

        http.formLogin()
               .successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"));

登录失败的:

1、编写新的handler代码

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private String url;

    public MyAuthenticationFailureHandler(String url) {
        this.url = url;
    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

配置类中的常用方法详解

http.authorizeRequests()
        // 放开不拦截 login.html和error.html页面和静态资源(这个一定要写在前面,不然报错)
        .antMatchers("/login.html").permitAll()
        // permitAll()表示全部人都可以访问
        .antMatchers("/img/**", "/css/**").permitAll()
        // 按照正则表达式放开不拦截
        .requestMatchers(".+[.]png")
        // 所有请求都需要认证
        .anyRequest().authenticated() ;

如果在yml中配置:(在所有controller都加前置路径xxx)
spring:
  mvc:
    servlet: 
      path: xxx

http.authorizeRequests()
        // 放开controller中login路径,前缀路径是xxx
        .mvcMatchers("/login").servletPath("xxx").permitAll();

  • 访问控制权限:
  • permitAll()                       全部人都可以访问
  • denyAll()                         禁止所有用户访问受保护的资源
  • anonymous                    可以匿名访问
  • authenticated()               允许已经通过身份验证的用户访问受保护的资源   
  • fullyAuthenticated()        需要完全认证(多出认证)才能访问
  • rememberMe()               记住我的功能(之后免密登录)                                                                                                                                                                                     

权限、角色、IP判断

权限访问:

1、在配置类中:
http.authorizeRequests()
        // 拥有admin权限才能访问admin.html
        .antMatchers("/admin.html").hasAnyAuthority("admin");

2、在UserDetailsServerImpl为用户设置拥有admin权限

@Service
public  class UserDetailsServerImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("执行了UserDetailsServerImpl中的loadUserByUsername方法");

        // 1.查询数据库判断用户是否存在,如果不存在,抛出异常
        if (!"admin".equals(username)){
            throw new UsernameNotFoundException("用户名不存在");
        }

        // 2.把查询出来的密码(注册时已经加密过)进行解析,或者直接把密码放到构造方法中
        String password = passwordEncoder.encode("123");

        // admin表示拥有管理员权限,normal表示拥有普通权限
        return new User(username, password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

角色判断:

1、在配置类中:

http.authorizeRequests()
        // 拥有admin权限才能访问admin.html
        .antMatchers("/admin.html").hasAnyAuthority("admin",);

2、在在UserDetailsServerImpl为用户设置角色

// admin和normal表示拥有管理员权限和普通权限,ROLE_a是设置角色a,必须以ROLE_开头
return new User(username, password,
        AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_a"));

IP判断:

http.authorizeRequests()
        // 只有127.0.0.1才能访问admin.html
        .antMatchers("/main.html").hasIpAddress("127.0.0.1")

自定义403处理

1、编写新的handle代码:

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler
{
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
        // 设置响应码403
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        // 设置响应头
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("{\"code\":\"403\", \"status\":\"error\", \"msg\":\"权限不足,请联系管理员\"}");
        writer.flush();
        writer.close();
    }
}

2、在配置类中引用:

    @Autowired
    MyAccessDeniedHandler myAccessDeniedHandler;

    // 异常处理
    http.exceptionHandling()
            .accessDeniedHandler(myAccessDeniedHandler);

自定义实现权限控制(结合access)

之前是使用http.authorizeRequests()..anyRequest().authenticated()来配置所有请求都认证,现在我们自定义实现权限的控制

1、编写server类:

@Service
public class MyServerImpl implements MyServer {
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {

        Object obj = authentication.getPrincipal();

        if (obj instanceof UserDetails){
            UserDetails userDetails = (UserDetails) obj;
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
        }

        return false;
    }
}

2、编写配置类:

 http.authorizeRequests()                   
       .anyRequest().access("@myServerImpl.hasPermission(request,authentication)");

3、在UserDetailsServerImpl类中给用户设置权限:

return new User(username, password,
       AuthorityUtils.commaSeparatedStringToAuthorityList(
                 "admin,normal,ROLE_a,/admin.html"));

使用注解配置

使用注解配置需要先在启动类或者配置类中添加@EnableGlobalMethodSecurity(securedEnabled = true) 的注解来开启注解配置

  • @Secured("ROLE_a") :在类或者方法上使用,多在Controller中的方法上对接口使用,表示  只有a 角色才能访问,否则报500
  • @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)注解中的prePostEnabled = true表示开启方法执行前验证,比如在controller中:
    @PreAuthorize("hasRole('a')") // 只有a角色才能访问
    @RequestMapping ("/toMain")
    public String toMain() {
    return "redirect:main.html";
    }

RememberMe - 记住我功能

1、该功能需要JDBC和mysql驱动的依赖,可以导入mybatis和mysql驱动的依赖来间接导入

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!-- mysql数据库依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

2、编写BeanConfig

@Configuration
public class BeanConfig {

    @Autowired
    private DataSource dataSource;

    // 用于自定义登录逻辑中的密码加密和验证密码
    @Bean
    public PasswordEncoder passwordEncoder (){
        return new BCryptPasswordEncoder();
    }

    // 自定义记住我的功能持久化
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        // 设置数据源
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动创建表,第一次启动时候需要,第二次启动需要注释掉,否则报错
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

}

3、在SecurityConfig类中配置该功能

    @Autowired
    private UserDetailsServerImpl userDetailsServer;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;  
      
    // 记住我功能
    http.rememberMe()
            // 设置过期时间,单位是秒
            .tokenValiditySeconds(60)
            // 自定义登录逻辑
            .userDetailsService(userDetailsServer)
            // 持久层对象
            .tokenRepository(persistentTokenRepository);

4、在login.html页面中添加记住我的单选框,

<form action="/login" method="post">
  用户名 <input type="text" name="username"> <br/>
  密码   <input type="password" name="password"> <br/>
  记住我 <input type="checkbox" name="remember-me" value="true" /> <br/>
  <input type="submit" value="登录"> <br/>
</form>

表单中name值只能是remember-me,除非去SecurityConfig中修改配置,当勾选记住我并登陆成功时,数据库会新增数据

在Thymeleaf中获取security属性值和判断权限

1、导入thymeleaf 相关依赖

        <!-- thymeleaf SpringSecurity5 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>
        <!-- thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

2、在templates目录下创建demo.html

<!DOCTYPE html>
<!--添加HTML、Thymeleaf的Spring Security扩展模块的命名空间-->
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf</title>
</head>
<body>
<!--获取security中的属性值-->
登录账号:<span sec:authentication="name"></span> <br/>
登录账号:<span sec:authentication="principal.username"></span> <br/>
凭证:<span sec:authentication="credentials"></span> <br/>
权限和角色:<span sec:authentication="authorities"></span> <br/>
客户端地址:<span sec:authentication="details.remoteAddress"></span> <br/>
sessionId:<span sec:authentication="details.sessionId"></span> <br/>
<br>

<!--根据security中用户权限来显示按钮-->
通过权限判断:
<button sec:authorize="hasAuthority('insert')">新增</button>
<button sec:authorize="hasAuthority('delete')">删除</button>
<button sec:authorize="hasAuthority('update')">修改</button>
<button sec:authorize="hasAuthority('select')">查看</button><br/>
通过角色判断:
<button sec:authorize="hasRole('a')">新增</button>
<button sec:authorize="hasRole('a')">删除</button>
<button sec:authorize="hasRole('a')">修改</button>
<button sec:authorize="hasRole('a')">查看</button>
</body>
</html>

3、在UserDetailsServerImpl的loadUserByUsername方法中释放权限

 return new User(username, password,
                AuthorityUtils.commaSeparatedStringToAuthorityList(
                        "admin,normal,ROLE_a,/admin.html,insert,delete"));

4、编写controller代码

    @RequestMapping("/demo")
    public String thymeleafDemo() {
        return "demo";
    }

登录访问后

退出登录

1、在main.html中添加security的登录退出超链接

<a href="/logout">退出登录</a>

2、在SecurityConfig类中配置该功能

        //退出登录
        http.logout()
                // 退出成功后跳转到login.html
               .logoutSuccessUrl("/login.html");

CSRF跨站保护

CSRF 跨站请求伪造,也被称为“OneClick Attack"或者Session Riding通过伪
造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
客户端与服务进行交互时,由于 http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意劫持通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。
1、在templates目录下创建login.html,并加入隐藏_csrf.token隐藏输入框

<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
templates中的login.html
<form action="/login" method="post">
    <!--  隐藏输入框,value为security生成的_csrf.token,如果_csrf为空就不填入这个值 -->
    <input type="hidden" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}"/>
    用户名 <input type="text" name="username"> <br/>
    密码   <input type="password" name="password"> <br/>
    记住我 <input type="checkbox" name="remember-me" value="true" /> <br/>
    <input type="submit" value="登录"> <br/>
</form>
</body>
</html>

2、编写controller

    @RequestMapping("/showLogin")
    public String showLogin() {
        return "login";
    }

 3、编写SecurityConfig中的configure方法

        // 关闭csrf保护,类似防火墙
//        http.csrf().disable();

        // 表单提交
        http.formLogin()
                // 自定义登录页面路径
                .loginPage("/showLogin")
                // 当发现是login请求时,去执行UserDetailsServerImpl,必须和html表单的请求路径一样
                .loginProcessingUrl("/login")

        // 授权认证
        http.authorizeRequests()
                // 放开不拦截 login.html和error.html页面和静态资源(这个一定要写在前面,不然报错)
                .antMatchers("/showLogin").permitAll()

        //退出登录
        http.logout()
                // 退出成功后跳转到login.html
               .logoutSuccessUrl("/showLogin.html");
    

登录时会自动获取_csrf字段的值填入,且_csrf的值每次登陆都会变化

Oauth2协议

简介

       第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。
       OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP、JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而OAUTH是简易的。互联网很多服务如Open API,很多大公司Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。

优点:

  • 更安全,客户端不接触用户密码,服务器端更易集中保护广泛传播并被持续采用
  • 短寿命和封装的token
  • 资源服务器和授权服务器解耦集中式授权,简化客户端
  • HTTP/IJSON友好,易于请求和传递token考虑多种客户端架构场景
  • 客户可以具有不同的信任级别

缺点:

  • 协议框架太宽泛,造成各种实现的兼容性和互操作性差
  • 不是一个认证协议,本身并不能告诉你任何用户信息
     

Oauth2四种授权模式

  • 授权码模式(该模式用得最多,也是最难最复杂的)
  • 密码模式
  • 客户端模式
  • 隐式授权模式 / 简化授权模式

具体可以看详解,点击跳转:授权模式详解

授权码模式演示

1、编写授权服务器配置AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer // 开启oauth2客户端模式(授权服务器模式)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                // 客户端id (一般由服务器生成),这里演示,使用固定值
                .withClient("admin")
                // 客户端密码,这里演示,使用固定值
                .secret(passwordEncoder.encode("112233"))
                // 访问token有效时间,单位秒
                .accessTokenValiditySeconds(3600)
                // 配置uri,用于授权成功后跳转
                .redirectUris("http://www.baidu.com")
                // 配置申请的授权范围
                .scopes("all")
                // 授权模式为授权码模式
                .authorizedGrantTypes("authorization_code");
    }
}

2、编写资源服务器配置ResourcesServerConfig 

@Configuration
@EnableResourceServer
public class ResourcesServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/user/**");
    }
}

3、编写配置BeanConfig

@Configuration
public class BeanConfig {
    // 用于自定义登录逻辑中的密码加密和验证密码
    @Bean
    public PasswordEncoder passwordEncoder (){
        return new BCryptPasswordEncoder();
    }

}

4、编写SecurityConfig配置类

@Configuration
// 开启使用注解security的方法级别的权限控制
//@EnableGlobalMethodSecurity(securedEnabled = true,  prePostEnabled = true)
// 开启Web安全性支持,Spring Security的默认配置将会被应用
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf保护,类似防火墙
        http.csrf().disable()
                // 以下是授权认证
                .authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll()
                // 所有请求都需要认证
                .anyRequest().authenticated()
                // 连接一起写
                .and()
                // 运行所有表单请求
                .formLogin().permitAll();

    }
}

5、自定义实现User类

public class User implements UserDetails {

    private String username;
    private String password;
    private List<GrantedAuthority> authorities;

    public User(String username, String password, List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

6、自定义登录逻辑

import com.securityoauth2demo.pojo.User;
@Service
public class UserServer implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123456");
        return new User("admin", password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

7、编写controller测试携token令牌访问

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {

        return authentication.getPrincipal();
    }

}

8、访问http://127.0.0.1:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all,验证成功后在跳转url中code后的值来获取授权码

9、用授权码区获取token令牌
      在Auth选项中选择Basic Auth

10、拿着token令牌就可以访问controller资源了

密码模式演示

1、在SecurityConfig配置类中添加密码模式要使用的Bean

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

2、编写授权服务器配置AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer // 开启oauth2客户端模式(授权服务器模式)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserServer userServer;

    /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userServer);
    }

    // 配置客户端信息,根据配置信息访问url来获取授权码,这里的url是:
    // http://127.0.0.1:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                // 客户端id (一般由服务器生成),这里演示,使用固定值
                .withClient("admin")
                // 客户端密码,这里演示,使用固定值
                .secret(passwordEncoder.encode("112233"))
                // 访问token有效时间,单位秒
                .accessTokenValiditySeconds(3600)
                // 配置uri,用于授权成功后跳转
                .redirectUris("http://www.baidu.com")
                // 配置申请的授权范围
                .scopes("all")
                // 授权模式为授权码模式
//                .authorizedGrantTypes("authorization_code")
                // 授权模式为密码模式
                .authorizedGrantTypes("password");
    }


}

3、密码模式访问获取token令牌

4、拿着token令牌就可以访问资源了

Redis存储Token

1、导入maven依赖

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--    commons-pool2对象池    -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

2、编写RedisConfig类

@Configuration
public class RedisConfig {

    // redis连接工厂
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 自动将token存储到redis中
     * @return
     */
    @Bean
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

}

3、在授权服务器配置AuthorizationServerConfig中新增代码

    @Autowired //按类型注入
    @Qualifier("redisTokenStore") //按名字注入
    private TokenStore tokenStore;

    /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userServer)
                .tokenStore(tokenStore);
    }

4、获取token的时候自动存入redis

JWT - demo

1、导依赖

        <!--    JWT    -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

2、编写Test

    /**
     * JWT生成token
     */
    @Test
    void testJwtBuilder() {
        // 当前时间
        long now = System.currentTimeMillis();
        // 过期时间为10分钟
        long exp = now + 60 * 1000 * 10;
        // 构建JWT对象
        JwtBuilder jwtBuilder = Jwts.builder();

        jwtBuilder
                // 声明的标识,相当于{"jwi":"8888"}
                .setId("8888")
                // 主体,用户 {"sub:":"Rose"}
                .setSubject("Rose")
                // 创建时间
                .setIssuedAt(new Date())
                // 算法选用HS256,盐是xxxx
                .signWith(SignatureAlgorithm.HS256, "xxxx")
                // 设置过期时间
                .setExpiration(new Date(exp))
                // 自定义声明
                .claim("roles", "admin")
                .claim("logo", "xxx.jpg");

        // 获取JWT的token
        String token = jwtBuilder.compact();
        System.out.println(token);

        String[] split = token.split("\\.");
        System.out.println("=====================================================");
        // 头
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
        // 主体
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        // 签名部分无法解密
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
    }

    /**
     * 解析token
     */
    @Test
    void testParseToken() {
        String token = "";
        Claims claims = Jwts.parser()
                // 签名秘钥(盐)
               .setSigningKey("xxxx")
               .parseClaimsJws(token)
               .getBody();

        System.out.println( "id : "+claims.getId());
        System.out.println("subject : "+claims.getSubject());
        System.out.println("issuedAt : "+claims.getIssuedAt());
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("签发时间:"+simpleDateFormat.format(claims.getIssuedAt()));
        System.out.println("过期时间:"+simpleDateFormat. format(claims .getExpiration()));
        System.out.println("当前时间: "+simpleDateFormat.format(new Date()));
        System.out.println("roles:"+claims.get("roles"));
        System.out.println("logo:"+claims.get("logo"));
    }

SecurityOauth2整合Jwt

1、注释掉redis的依赖和配置代码

2、创建JwtTokenStoreConfig配置类

@Configuration
public class JwtTokenStoreConfig {

    @Bean
    public TokenStore jwtTokenstore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     *
     * jwt与oauth2的token转换器
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        //配置JwT使用的秘钥
        accessTokenConverter.setSigningKey("test_key");
        return accessTokenConverter;
    }

}

3、修改AuthorizationServerConfig配置

    @Autowired
    @Qualifier("jwtTokenstore")
    private TokenStore tokenStore;
    
    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;
  
  /**
     * 使用密码模式所需配置
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userServer)
                // 配置存储令牌策略
                .tokenStore(tokenStore)
                // 配置令牌转换器
                .accessTokenConverter(jwtAccessTokenConverter);
    }

5、获取token来访问资源

扩展jwt中的存储内容

1、创建JwtTokenEnhancer配置类

@Configuration
public class JwtTokenEnhancer implements TokenEnhancer {
    /**
     * Jwt token内容增强
     * @param oAuth2AccessToken
     * @param oAuth2Authentication
     * @return
     */
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> info = new HashMap<>();
        info.put("enhance","enhance info");
        ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info);
        return oAuth2AccessToken;
    }
}

2、在JwtTokenStoreConfig配置类中添加Bean

    /**
     * 自定义jwt的token增强器
     * @return
     */
    @Bean(name = "JwtTokenStoreConfig-JwtTokenEnhancer") //依赖包中已经由同名的Bean,所有重命名
    public JwtTokenEnhancer jwtTokenEnhancer() {
        return new JwtTokenEnhancer();
    }

3、修改AuthorizationServerConfig授权服务器配置

    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置JWT内容增强器
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        tokenEnhancerChain.setTokenEnhancers(delegates);

        // token配置
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userServer)
                // 配置存储令牌策略
                .tokenStore(tokenStore)
                // 配置jwt与security的令牌转换器
                .accessTokenConverter(jwtAccessTokenConverter)
                // 配置JWT内容增强器
                .tokenEnhancer(tokenEnhancerChain);
    }

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值