SpringCloud oauth整合JWT(一) -- 思路介绍与认证服务器实现

注意:这里只是讲解oauth与jwt整合,不过多的介绍单个技术

1.oauth 2.0

OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。

2.JWT

JSON Web Token(JWT)是一个非常轻巧的规范,这个规范允许我们使用JWT在用户与服务器传递安全可靠的信息。

3.为什么需要两者结合

首先,这些的前提都是在分布式服务的前提下,如果你的服务或者是应用不考虑分布式,甚至是不考虑用户的使用感受,那么你可以有无数种实现权限控制的方式,但如果如博主所说需要oauth与jwt,那么就说明你的服务是复杂的,是需要更加严谨的权限控制,那么请读者仔细的阅读博文的以下内容。

  • 单独使用jwt:进行加密后会生成一串字符串,可以用加密的秘钥进行解密,可以将用户信息进行加密当做token进行登录的验证(只能存储非隐私的信息),但是无法完成对某个特定权限的控制和对某些接口的权限限制,短板较多且不够灵活。
  • 单独使用oauth:在单个项目组成的项目中,oauth可以完成认证与授权,但在多服务的情况下,做单点登录的时候需要不停的访问认证服务器,这时候或造成认证服务器的高压,且会影响接口的响应效率
  • 两者结合:jwt将用户的信息加密后生成有意义的token,而不是oauth那样生成的没有任何意义的UUID,然后oauth又有很优秀的权限控制体系,直接用jwt的token就可以下多个服务间进行使用,结合jwt的token****于oauth的权限控制,可以加强服务安全体系的完善。

3.认证服务器

1.pom依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.0.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
    </dependencies>

2.security配置

package com.wangle.server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 注入用户自定义认证逻辑
    @Autowired
    private MyUserDetailService userDetailService;

    @Autowired
    private PasswordEncoder passwordEncoder;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers("/**")
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService)
                .passwordEncoder(passwordEncoder); //密码加密
    }
}

2.tokenStore配置

package com.wangle.server.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.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Configuration
public class TokenStoreConfig {

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

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

    @Bean
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("test_key");//配置JWT使用的秘钥
        return accessTokenConverter;
    }

    @Bean
    public JwtTokenEnhancer jwtTokenEnhancer() {
        return new JwtTokenEnhancer();
    }

}

3.认证配置

package com.wangle.server.config;

import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.nio.charset.StandardCharsets;
import java.util.*;

@Configuration
@EnableAuthorizationServer
public class ServerConfig extends AuthorizationServerConfigurerAdapter {

    // 注入认证管理器
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private TokenStore redisTokenStore;

    // 注入用户自定义认证逻辑
    @Autowired
    private MyUserDetailService userDetailService;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenStore jwtTokenStore;

    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    //定义客户端的信息
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("wangle")
                .secret(passwordEncoder.encode("wangle"))
                // .resourceIds("admin","auth")
                //设置token的有效期,不设置默认12小时
                //.accessTokenValiditySeconds(client.getAccessTokenValidatySeconds())
                //设置刷新token的有效期,不设置默认30天
                //.refreshTokenValiditySeconds(client.getRefreshTokenValiditySeconds())
                .redirectUris("http://www.baidu.com")
                .authorizedGrantTypes("authorization_code","client_credentials", "refresh_token", "password")
                .scopes("all", "read", "write")
                .accessTokenValiditySeconds(10)
                .autoApprove(true);
    }


    //自定义web响应错误翻译器
    /*
     * @Bean public WebResponseExceptionTranslator webResponseExceptionTranslator(){
     * return new MssWebResponseExceptionTranslator(); }
     */

    /**
     * <p>
     * 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,
     * </p>
     *
     * @return
     */
    @Primary
    @Bean
    public DefaultTokenServices myTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(jwtTokenStore);
        tokenServices.setSupportRefreshToken(true);
        // tokenServices.setClientDetailsService(clientDetails());
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
        // refresh_token默认30天
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer); //配置JWT的内容增强器
        delegates.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(delegates);
        endpoints
                .userDetailsService(userDetailService)
                //定义权限终点的token存储方式
                .tokenStore(redisTokenStore)
                //定义权限终点的user认证逻辑
                .accessTokenConverter(jwtAccessTokenConverter)
                //定义权限终点的认证管理器
                .authenticationManager(authenticationManager)
                //jwt增强
                //.tokenEnhancer(enhancerChain)
                //认证异常翻译
                //endpoints.exceptionTranslator(webResponseExceptionTranslator());
        ;
    }
}

ClientDetailsServiceConfigurer可以使用以上的,也可以配置成从数据库获取,具体的实现方式这里不做介绍。

3.自定义用户验证实现

package com.wangle.server.config;

import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.HashSet;

@Service("userDetailService")
public class MyUserDetailService implements UserDetailsService {

	@Autowired
	private PasswordEncoder passwordEncoder;

	// 重写loadUserByUsername(实现用户认证逻辑)
	@Override
	public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {

		// 初始化权限集合
		HashSet<GrantedAuthority> authorities = new HashSet<>();
		// 可用性 :true:可用 false:不可用
		boolean enabled = true;
		// 过期性 :true:没过期 false:过期
		boolean accountNonExpired = true;
		// 有效性 :true:凭证有效 false:凭证无效
		boolean credentialsNonExpired = true;
		// 锁定性 :true:未锁定 false:已锁定
		boolean accountNonLocked = true;

		// 角色名必须是以ROLE_开头,不然权限注解无法识别此角色
		SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN");
		authorities.add(grantedAuthority);

		User principle = new User(mobile,passwordEncoder.encode("123456"), enabled, accountNonExpired,
				credentialsNonExpired, accountNonLocked, authorities);
		return principle;
	}
}

4.请求地址

请求获取token的地址就可以获取token
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王乐乐君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值