• Oauth2认证及Spring Security Oauth2授权码模式


    Oauth2认证

    Oauth2简介

    简介

    第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。

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

    Oauth协议目前发展到2.0版本,1.0版本过于复杂,2.0版本已得到广泛应用。

    参考:https://baike.baidu.com/item/oAuth/7153134?fr=aladdin

    Oauth 协议:https://tools.ietf.org/html/rfc6749

    下边分析一个Oauth2认证的例子,网站使用微信认证的过程:

    在这里插入图片描述

    1. 用户进入网站的登录页面,点击微信的图标以微信账号登录系统,用户是自己在微信里信息的资源拥有者。
      在这里插入图片描述

    2. 点击“微信”出现一个二维码,此时用户扫描二维码,开始给网站授权。

    3. 资源拥有者同意给客户端授权
       资源拥有者扫描二维码表示资源拥有者同意给客户端授权,微信会对资源拥有者的身份进行验证,验证通过后,微信会询问用户是否给授权网站访问自己的微信数据,用户点击“确认登录”表示同意授权,微信认证服务器会颁发一个授权码,并重定向到网站。

    4. 客户端获取到授权码,请求认证服务器申请令牌
       此过程用户看不到,客户端应用程序请求认证服务器,请求携带授权码。

    5. 认证服务器向客户端响应令牌
       认证服务器验证了客户端请求的授权码,如果合法则给客户端颁发令牌,令牌是客户端访问资源的通行证。此交互过程用户看不到,当客户端拿到令牌后,用户在网站看到已经登录成功。

    6. 客户端请求资源服务器的资源
       客户端携带令牌访问资源服务器的资源。网站携带令牌请求访问微信服务器获取用户的基本信息。

    7. 资源服务器返回受保护资源
       资源服务器校验令牌的合法性,如果合法则向用户响应资源信息内容。
      注意:资源服务器和认证服务器可以是一个服务也可以分开的服务,如果是分开的服务资源服务器通常要请求认证服务器来校验令牌的合法性。

    Oauth2.0认证流程如下:
    引自Oauth2.0协议rfc6749 https://tools.ietf.org/html/rfc6749
    在这里插入图片描述

    角色

    客户端

    本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:Android客户端、Web客户端(浏览器端)、微信客户端等。

    资源拥有者

    通常为用户,也可以是应用程序,即该资源的拥有者。

    授权服务器(也称认证服务器)

    用来对资源拥有的身份进行认证、对访问资源进行授权。客户端要想访问资源需要通过认证服务器由资源拥有者授权后方可访问。

    资源服务器

    存储资源的服务器,比如,网站用户管理服务器存储了网站用户信息,网站相册服务器存储了用户的相册信息,微信的资源服务存储了微信的用户信息等。客户端最终访问资源服务器获取资源信息。

    常用术语

    • 客户凭证(client Credentials):客户端的clientId和密码用于认证客户
    • 令牌(tokens):授权服务器在接收到客户请求后,颁发的访问令牌
    • 作用域(scopes):客户请求访问令牌时,由资源拥有者额外指定的细分权限(permission)

    令牌类型

    • 授权码:仅用于授权码授权类型,用于交换获取访问令牌和刷新令牌
    • 访问令牌:用于代表一个用户或服务直接去访问受保护的资源
    • 刷新令牌:用于去授权服务器获取一个刷新访问令牌
    • BearerToken:不管谁拿到Token都可以访问资源,类似现金
    • Proof of Possession(PoP) Token:可以校验client是否对Token有明确的拥有权

    特点

    优点:

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

    缺点:

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

    Spring Security Oauth2授权码模式

    1. 创建项目
    2. 导入依赖
     <dependency>
             <groupId>org.springframework.cloudgroupId>
             <artifactId>spring-cloud-starter-oauth2artifactId>
          dependency>
          <dependency>
             <groupId>org.springframework.cloudgroupId>
             <artifactId>spring-cloud-starter-securityartifactId>
          dependency>
          <dependencyManagement>
          <dependencies>
             <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>${spring-cloud.version}version>
                <type>pomtype>
                <scope>importscope>
             dependency>
          dependencies>
       dependencyManagement>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3、编写实体类

    package com.zuxia.entity;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.List;
    
    /**
     * 用户类
     */
    public class User implements UserDetails {
        private String username;
        private String password;
        private List<GrantedAuthority> authorities;
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return 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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    4.编写配置类
    SecurityConfig.java

    package com.zuxai.springsecurityoauth2demo.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    /**
     * Spring Security 配置类
     *
     */
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
       @Bean
       public PasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder();
       }
    
    
       @Override
       public void configure(HttpSecurity http) throws Exception {
          http.csrf()
                .disable()//关闭csrf
                .authorizeRequests()//授权
                .antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll()//放行授权服务器端点
                .anyRequest().authenticated()//拦截所有的请求
                .and()//通过and()可以配置多个http
                .formLogin().permitAll();//放行所有的表单请求
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    5.编写自定义登录逻辑

    UserService.java

    package com.zuxia.springsecurityoauth2demo.service;
    
    import com.yjxxt.springsecurityoauth2demo.pojo.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.AuthorityUtils;
    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 UserService 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"));
       }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    6.授权服务器配置
    AuthorizationServerConfig

    package com.zuxia.springsecurityoauth2demo.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    
    /**
     * 授权服务器配置
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {		
        	//获取授权码
    		//http://localhost:8080/oauth/authorizeresponse_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all 
            clients.inMemory()
                    //配置client_id
                    .withClient("admin")
                    //配置client-secret
                    .secret(passwordEncoder.encode("112233"))
                    //配置redirect_uri,用于授权成功后跳转
                    .redirectUris("http://www.baidu.com")
                    //配置申请的权限范围
                    .scopes("all")
                    //配置grant_type,表示授权类型  authorization_code授权码模式
                    .authorizedGrantTypes("authorization_code");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    7.资源服务器配置
    ResourceServerConfig.java

    package com.zuxia.springsecurityoauth2demo.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    
    /**
     * 资源服务器配置  
     * 负责放行资源的路径
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()//授权
                    .anyRequest().authenticated()//都进行认证
                    .and()
                    .requestMatchers().antMatchers("/user/**");//配置需要保护的资源路径
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    8.配置资源类

    package com.zuxia.springsecurityoauth2demo.controller;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
       @GetMapping("/getCurrentUser")
       public Object getCurrentUser(Authentication authentication) {
          return authentication.getPrincipal();//返回当前登录用户的具体对象
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    9.测试
    获取授权码
    http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all

    ///oauth/authorize:授权服务器的授权端点
    //response_type:返回类型
    后面三个参数对应授权服务器配置中的配置
    在这里插入图片描述

    输入账户密码
    在这里插入图片描述

    点击授权获取授权码
    在这里插入图片描述

    根据授权码获取令牌(POST请求)
    在这里插入图片描述
    在这里插入图片描述

    • grant_type:授权类型,填写authorization_code,表示授权码模式
    • code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。
    • client_id:客户端标识
    • redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。
    • scope:授权范围。

    认证失败服务端返回 401 Unauthorized

    注意:此时无法请求到令牌,访问服务器会报错
    在这里插入图片描述

    根据token去资源服务器拿资源
    在这里插入图片描述

    如果修改token就会报错
    在这里插入图片描述

    Spring Security Oauth2 密码模式

    在上面的代码中进行适当的修改即可

    SecurityConfig.java

    package com.zuxia.springsecurityoauth2demo.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    /**
     * Spring Security 配置类
     *
     */
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
       @Bean
       public PasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder();
       }
    	//使用密码模式需要配置
       @Bean
       @Override
       public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
       }
    
       @Override
       public void configure(HttpSecurity http) throws Exception {
          http.csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "/logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    AuthorizationServerConfig.java

    package com.zuxia.springsecurityoauth2demo.config;
    
    import com.yjxxt.springsecurityoauth2demo.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    
    /**
     * 授权服务器配置
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private UserService userService;
    
        /**
         * 使用密码模式需要配置
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints
              		//授权管理器
              		.authenticationManager(authenticationManager)
              		//自定义登录逻辑
                    .userDetailsService(userService);
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    //配置client_id
                    .withClient("admin")
                    //配置client-secret
                    .secret(passwordEncoder.encode("112233"))
                    //配置访问token的有效期
                    .accessTokenValiditySeconds(3600)
                    //配置刷新token的有效期
                    .refreshTokenValiditySeconds(864000)
                    //配置redirect_uri,用于授权成功后跳转
                    .redirectUris("http://www.baidu.com")
                    //配置申请的权限范围
                    .scopes("all")
                    //配置grant_type,表示授权类型
                    .authorizedGrantTypes("authorization_code","password");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    测试:
    在这里插入图片描述
    在这里插入图片描述

    在Redis中存储token

    之前的代码我们将token直接存在内存中,这在生产环境中是不合理的,下面我们将其改造成存储在Redis中

    1.添加依赖及配置

    
    <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
    <dependency>
       <groupId>org.apache.commonsgroupId>
       <artifactId>commons-pool2artifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    application.properties

    # Redis配置
    spring.redis.host=192.168.10.100
    
    • 1
    • 2

    编写Redis配置类

    RedisConfig.java

    package com.zuxia.springsecurityoauth2demo.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
    
    /**
     * 使用redis存储token的配置
     */
    @Configuration
    public class RedisConfig {
       @Autowired//注入redis的连接工厂
       private RedisConnectionFactory redisConnectionFactory;
    
       @Bean
       public TokenStore redisTokenStore(){
          return new RedisTokenStore(redisConnectionFactory);
       }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在认证服务器配置中指定令牌的存储策略为Redis

    package com.zuxia.springsecurityoauth2demo.config;
    
    import com.yjxxt.springsecurityoauth2demo.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    
    /**
     * 授权服务器配置
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private UserService userService;
    
        @Autowired
        @Qualifier("redisTokenStore")
        private TokenStore tokenStore;
    
        /**
         * 使用密码模式需要配置
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints
              		//权限管理器
              		.authenticationManager(authenticationManager)
              		//自定义登录逻辑
                    .userDetailsService(userService)
              		//令牌存储位置
                    .tokenStore(tokenStore);
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    //配置client_id
                    .withClient("admin")
                    //配置client-secret
                    .secret(passwordEncoder.encode("112233"))
                    //配置访问token的有效期
                    .accessTokenValiditySeconds(3600)
                    //配置刷新token的有效期
                    .refreshTokenValiditySeconds(864000)
                    //配置redirect_uri,用于授权成功后跳转
                    .redirectUris("http://www.baidu.com")
                    //配置申请的权限范围
                    .scopes("all")
                    //配置grant_type,表示授权类型
                    .authorizedGrantTypes("password");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    测试:

    使用密码模式请求token
    在这里插入图片描述

    JWT

    常见的认证机制

    HTTP Basic Auth

    HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth。

    Cookie Auth

    Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效。
     在这里插入图片描述

    OAuth

    OAuth(开放授权,Open Authorization)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。如网站通过微信、微博登录等,主要用于第三方登录。

    OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。

    下面是OAuth2.0的流程:
    在这里插入图片描述
    这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用。

    缺点:过重。

    Token Auth

    使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

    1. 客户端使用用户名跟密码请求登录
    2. 服务端收到请求,去验证用户名与密码
    3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
    4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
    5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
    6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
      在这里插入图片描述
      比第一种方式更安全,比第二种方式更节约服务器资源,比第三种方式更加轻量。

    具体,Token Auth的优点(Token机制相对于Cookie机制又有什么好处呢?):

    1. 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
    2. 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
    3. 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
    4. 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
    5. 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 10等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
    6. CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
    7. 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算的Token验证和解析要费时得多.
    8. 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
    9. 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

    JWT简介

    什么是JWT

    JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。

    官网: https://jwt.io/

    标准: https://tools.ietf.org/html/rfc7519

    JWT令牌的优点:

    1. jwt基于json,非常方便解析。
    2. 可以在令牌中自定义丰富的内容,易扩展。
    3. 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
    4. 资源服务使用JWT可不依赖认证服务即可完成授权。

    缺点:

    1. JWT令牌较长,占存储空间比较大。

    JWT组成

    一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

    头部(Header)

    头部用于描述关于该JWT的最基本的信息,例如其类型(即JWT)以及签名所用的算法(如HMAC SHA256或RSA)等。这也可以被表示成一个JSON对象。

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
    • 1
    • 2
    • 3
    • 4
    • typ:是类型。
    • alg:签名的算法,这里使用的算法是HS256算法

    我们对头部的json字符串进行BASE64编码(网上有很多在线编码的网站),编码后的字符串如下:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9  
    
    • 1

    Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中提供了非常方便的 BASE64Encoder和 BASE64Decoder,用它们可以非常方便的完成基于 BASE64 的编码和解码。

    负载(Payload)

    第二部分是负载,就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:

    • 标准中注册的声明(建议但不强制使用)

      iss: jwt签发者
      sub: jwt所面向的用户
      aud: 接收jwt的一方
      exp: jwt的过期时间,这个过期时间必须要大于签发时间
      nbf: 定义在什么时间之前,该jwt都是不可用的.
      iat: jwt的签发时间
      jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    • 公共的声明

    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    • 私有的声明

    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    这个指的就是自定义的claim。比如下面那个举例中的name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。

    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中sub是标准的声明,name是自定义的声明(公共的或私有的)

    然后将其进行base64编码,得到Jwt的第二部分:

     eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbWVzIiwiYWRtaW4iOnRydWV9  
    
    • 1

    提示:声明中不要放一些敏感信息。

    签证、签名(signature)

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    1. header (base64后的)
    2. payload (base64后的)
    3. secret(盐,一定要保密)

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:

    8HI-Lod0ncfVDnbKIPJJqLH998duF9DSDGkx3gRPNVI
    
    • 1

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

    eyJhbGciOiJIUzI1NiIsInR9cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.8HI-Lod0ncfVDnbKIPJJqLH998duF9DSDGkx3gRPNVI  
    
    • 1

    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

  • 相关阅读:
    面试题-01
    YOLOv5算法改进(20)— 如何去写YOLOv5相关的论文(包括论文阅读+规律总结+写作方法)
    虹科案例 | 空调故障无冷气,且没有故障码
    智能恒等于推荐系统
    压六类双绞线网线水晶头,
    nginx 代理接口报404 问题排查
    Leetcode21:合并两个有效链表
    数据结构体进阶链表【带头双向循环链表,单向链表的优化,从根部解决了顺序表的缺点】一文带你深入理解链表
    java计算机毕业设计-心理健康管理-源程序+mysql+系统+lw文档+远程调试
    振弦采集模块(智能振弦传感器测量模块)其它常见问题
  • 原文地址:https://blog.csdn.net/qq_45429856/article/details/121480778