Spring Cloud OAuth2(一) 搭建授权服务

概要

本文内容主要为spring cloud 授权服务的搭建,采用jwt认证。
GitHub 地址:https://github.com/fp2952/spring-cloud-base/tree/master/auth-center/auth-center-provider

添加依赖

Spring Security 及 Security 的OAuth2 扩展

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

启动类注解

启动类添加 @EnableAuthorizationServer 注解

@SpringCloudApplication
@EnableAuthorizationServer
@EnableFeignClients("com.peng.main.client")
public class AuthCenterProviderApplication {
   public static void main(String[] args){
       SpringApplication.run(AuthCenterProviderApplication.class, args);
   }
}

Oauth2配置类AuthorizationServerConfigurerAdapter

AuthorizationServerConfigurerAdapter中:

  • ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
  • AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.
  • AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
    主要配置如下:

配置客户端详情信息(Client Details)

ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一个回调配置项) 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),Spring Security OAuth2的配置方法是编写@Configuration类继承AuthorizationServerConfigurerAdapter,然后重写void configure(ClientDetailsServiceConfigurer clients)方法,如:

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用JdbcClientDetailsService客户端详情服务
        clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

这里使用Jdbc实现客户端详情服务,数据源dataSource不做叙述,使用框架默认的表,schema链接:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

配置令牌 管理 (jwtAccessTokenConverter)

JwtAccessTokenConverter是用来生成token的转换器,而token令牌默认是有签名的,且资源服务器需要验证这个签名。此处的加密及验签包括两种方式:
对称加密、非对称加密(公钥密钥)
对称加密需要授权服务器和资源服务器存储同一key值,而非对称加密可使用密钥加密,暴露公钥给资源服务器验签,本文中使用非对称加密方式,配置于AuthorizationServerConfigurerAdapter如下:

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                // 配置JwtAccessToken转换器
                .accessTokenConverter(jwtAccessTokenConverter())
                // refresh_token需要userDetailsService
                .reuseRefreshTokens(false).userDetailsService(userDetailsService);
                //.tokenStore(getJdbcTokenStore());
    }
<span class="hljs-comment">/**
 * 使用非对称加密算法来对Token进行签名
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> JwtAccessTokenConverter <span class="hljs-title">jwtAccessTokenConverter</span><span class="hljs-params">()</span> </span>{

    <span class="hljs-keyword">final</span> JwtAccessTokenConverter converter = <span class="hljs-keyword">new</span> JwtAccessToken();
    <span class="hljs-comment">// 导入证书</span>
    KeyStoreKeyFactory keyStoreKeyFactory =
            <span class="hljs-keyword">new</span> KeyStoreKeyFactory(<span class="hljs-keyword">new</span> ClassPathResource(<span class="hljs-string">"keystore.jks"</span>), <span class="hljs-string">"mypass"</span>.toCharArray());
    converter.setKeyPair(keyStoreKeyFactory.getKeyPair(<span class="hljs-string">"mytest"</span>));

    <span class="hljs-keyword">return</span> converter;
}

通过 JDK 工具生成 JKS 证书文件,并将 keystore.jks 放入resource目录下
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore keystore.jks -storepass mypass

此处我们自定义JwtAccessToken用于添加额外用户信息

/**
 * Created by fp295 on 2018/4/16.
 * 自定义JwtAccessToken转换器
 */
public class JwtAccessToken extends JwtAccessTokenConverter {
<span class="hljs-comment">/**
 * 生成token
 * <span class="hljs-doctag">@param</span> accessToken
 * <span class="hljs-doctag">@param</span> authentication
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> OAuth2AccessToken <span class="hljs-title">enhance</span><span class="hljs-params">(OAuth2AccessToken accessToken, OAuth2Authentication authentication)</span> </span>{
    DefaultOAuth2AccessToken defaultOAuth2AccessToken = <span class="hljs-keyword">new</span> DefaultOAuth2AccessToken(accessToken);

    <span class="hljs-comment">// 设置额外用户信息</span>
    BaseUser baseUser = ((BaseUserDetail) authentication.getPrincipal()).getBaseUser();
    baseUser.setPassword(<span class="hljs-keyword">null</span>);
    <span class="hljs-comment">// 将用户信息添加到token额外信息中</span>
    defaultOAuth2AccessToken.getAdditionalInformation().put(Constant.USER_INFO, baseUser);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.enhance(defaultOAuth2AccessToken, authentication);
}

<span class="hljs-comment">/**
 * 解析token
 * <span class="hljs-doctag">@param</span> value
 * <span class="hljs-doctag">@param</span> map
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> OAuth2AccessToken <span class="hljs-title">extractAccessToken</span><span class="hljs-params">(String value, Map&lt;String, ?&gt; map)</span></span>{
    OAuth2AccessToken oauth2AccessToken = <span class="hljs-keyword">super</span>.extractAccessToken(value, map);
    convertData(oauth2AccessToken, oauth2AccessToken.getAdditionalInformation());
    <span class="hljs-keyword">return</span> oauth2AccessToken;
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">convertData</span><span class="hljs-params">(OAuth2AccessToken accessToken,  Map&lt;String, ?&gt; map)</span> </span>{
    accessToken.getAdditionalInformation().put(Constant.USER_INFO,convertUserData(map.get(Constant.USER_INFO)));

}

<span class="hljs-function"><span class="hljs-keyword">private</span> BaseUser <span class="hljs-title">convertUserData</span><span class="hljs-params">(Object map)</span> </span>{
    String json = JsonUtils.deserializer(map);
    BaseUser user = JsonUtils.serializable(json, BaseUser.class);
    <span class="hljs-keyword">return</span> user;
}

}

JwtAccessToken 类中从authentication里的getPrincipal(实际为UserDetails接口)获取用户信息,所以我们需要实现自己的UserDetails

/**
 * Created by fp295 on 2018/4/29.
 * 包装org.springframework.security.core.userdetails.User类
 */
public class BaseUserDetail implements UserDetails, CredentialsContainer {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> BaseUser baseUser;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> org.springframework.security.core.userdetails.User user;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BaseUserDetail</span><span class="hljs-params">(BaseUser baseUser, User user)</span> </span>{
    <span class="hljs-keyword">this</span>.baseUser = baseUser;
    <span class="hljs-keyword">this</span>.user = user;
}


<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">eraseCredentials</span><span class="hljs-params">()</span> </span>{
    user.eraseCredentials();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
    <span class="hljs-keyword">return</span> user.getAuthorities();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getPassword</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> user.getPassword();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getUsername</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> user.getUsername();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isAccountNonExpired</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> user.isAccountNonExpired();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isAccountNonLocked</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> user.isAccountNonLocked();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isCredentialsNonExpired</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> user.isCredentialsNonExpired();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isEnabled</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> user.isEnabled();
}

<span class="hljs-function"><span class="hljs-keyword">public</span> BaseUser <span class="hljs-title">getBaseUser</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> baseUser;
}

}

授权端点开放

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer
                // 开启/oauth/token_key验证端口无权限访问
                .tokenKeyAccess("permitAll()")
                // 开启/oauth/check_token验证端口认证权限访问
                .checkTokenAccess("isAuthenticated()");
    }

Security 配置

需要配置 DaoAuthenticationProvider、UserDetailService 等

@Configuration
@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
<span class="hljs-comment">// 自动注入UserDetailsService</span>
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> BaseUserDetailService baseUserDetailService;

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(HttpSecurity http)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    http    <span class="hljs-comment">// 配置登陆页/login并允许访问</span>
            .formLogin().permitAll()
            <span class="hljs-comment">// 登出页</span>
            .and().logout().logoutUrl(<span class="hljs-string">"/logout"</span>).logoutSuccessUrl(<span class="hljs-string">"/"</span>)
            <span class="hljs-comment">// 其余所有请求全部需要鉴权认证</span>
            .and().authorizeRequests().anyRequest().authenticated()
            <span class="hljs-comment">// 由于使用的是JWT,我们这里不需要csrf</span>
            .and().csrf().disable();
}

<span class="hljs-comment">/**
 * 用户验证
 * <span class="hljs-doctag">@param</span> auth
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">configure</span><span class="hljs-params">(AuthenticationManagerBuilder auth)</span> </span>{
    auth.authenticationProvider(daoAuthenticationProvider());
}


<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> DaoAuthenticationProvider <span class="hljs-title">daoAuthenticationProvider</span><span class="hljs-params">()</span></span>{
    DaoAuthenticationProvider provider = <span class="hljs-keyword">new</span> DaoAuthenticationProvider();
    <span class="hljs-comment">// 设置userDetailsService</span>
    provider.setUserDetailsService(baseUserDetailService);
    <span class="hljs-comment">// 禁止隐藏用户未找到异常</span>
    provider.setHideUserNotFoundExceptions(<span class="hljs-keyword">false</span>);
    <span class="hljs-comment">// 使用BCrypt进行密码的hash</span>
    provider.setPasswordEncoder(<span class="hljs-keyword">new</span> BCryptPasswordEncoder(<span class="hljs-number">6</span>));
    <span class="hljs-keyword">return</span> provider;
}

}

UserDetailsService 实现

@Service
public class BaseUserDetailService implements UserDetailsService {
<span class="hljs-keyword">private</span> Logger logger = LoggerFactory.getLogger(<span class="hljs-keyword">this</span>.getClass());

<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> BaseUserService baseUserService;
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> BaseRoleService baseRoleService;

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> UserDetails <span class="hljs-title">loadUserByUsername</span><span class="hljs-params">(String username)</span> <span class="hljs-keyword">throws</span> UsernameNotFoundException </span>{

    <span class="hljs-comment">// 调用FeignClient查询用户</span>
    ResponseData&lt;BaseUser&gt; baseUserResponseData = baseUserService.getUserByUserName(username);
    <span class="hljs-keyword">if</span>(baseUserResponseData.getData() == <span class="hljs-keyword">null</span> || !ResponseCode.SUCCESS.getCode().equals(baseUserResponseData.getCode())){
        logger.error(<span class="hljs-string">"找不到该用户,用户名:"</span> + username);
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UsernameNotFoundException(<span class="hljs-string">"找不到该用户,用户名:"</span> + username);
    }
    BaseUser baseUser = baseUserResponseData.getData();

    <span class="hljs-comment">// 调用FeignClient查询角色</span>
    ResponseData&lt;List&lt;BaseRole&gt;&gt; baseRoleListResponseData = baseRoleService.getRoleByUserId(baseUser.getId());
    List&lt;BaseRole&gt; roles;
    <span class="hljs-keyword">if</span>(baseRoleListResponseData.getData() == <span class="hljs-keyword">null</span> ||  !ResponseCode.SUCCESS.getCode().equals(baseRoleListResponseData.getCode())){
        logger.error(<span class="hljs-string">"查询角色失败!"</span>);
        roles = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();
    }<span class="hljs-keyword">else</span> {
        roles = baseRoleListResponseData.getData();
    }

    <span class="hljs-comment">// 获取用户权限列表</span>
    List&lt;GrantedAuthority&gt; authorities = <span class="hljs-keyword">new</span> ArrayList();
    roles.forEach(e -&gt; {
        <span class="hljs-comment">// 存储用户、角色信息到GrantedAuthority,并放到GrantedAuthority列表</span>
        GrantedAuthority authority = <span class="hljs-keyword">new</span> SimpleGrantedAuthority(e.getRoleCode());
        authorities.add(authority);
    
    });

    <span class="hljs-comment">// 返回带有用户权限信息的User</span>
    org.springframework.security.core.userdetails.User user =  <span class="hljs-keyword">new</span> org.springframework.security.core.userdetails.User(baseUser.getUserName(),
            baseUser.getPassword(), isActive(baseUser.getActive()), <span class="hljs-keyword">true</span>, <span class="hljs-keyword">true</span>, <span class="hljs-keyword">true</span>, authorities);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BaseUserDetail(baseUser, user);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isActive</span><span class="hljs-params">(<span class="hljs-keyword">int</span> active)</span></span>{
    <span class="hljs-keyword">return</span> active == <span class="hljs-number">1</span> ? <span class="hljs-keyword">true</span> : <span class="hljs-keyword">false</span>;
}

}

授权服务器验证

http://127.0.0.1:8080/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=www.baidu.com

注意:client_id:为存储在数据库里的client_id, response_type:写死code

  1. 链接回车后进入spring security 的简单登陆页面,

  1. 输入账号密码,为实现的 UserDetailsService 要里获取的用户,点击 login,
  2. 进入简单授权页面,点击 Authorize,
  3. 重定向到 redirect_uri,并带有 code 参数:
    http://www.baidu.com?code=rTKETX
  4. post请求获取 token:

注意,此处需加 Authorization 请求头,值为 Basic xxx xxx 为 client_id:client_secret 的 base64编码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Cloud OAuth2 Gateway是基于Spring Cloud Gateway和Spring Security OAuth2的网关服务,用于保护和管理微服务的访问权限。它提供了一种统一的认证和授权机制,可以集成多种认证方式,如基于密码、令牌、JWT等的认证方式,同时也支持多种授权方式,如基于角色、权限等的授权方式。通过Spring Cloud OAuth2 Gateway,可以实现微服务的安全访问和管理,提高系统的安全性和可靠性。 ### 回答2: Spring Cloud OAuth2 Gateway 是基于 Spring Cloud 的一种解决方案,用于构建微服务架构下的网关服务Spring Cloud OAuth2 Gateway 并不直接处理身份认证和授权,而是作为一个网关服务,负责转发请求和处理反向代理等功能。它集成了 Spring Cloud 的相关组件,比如 Eureka、Ribbon 和 Zuul,通过这些组件的协同作用,可以实现服务的负载均衡、熔断、动态路由等功能。 在 Spring Cloud OAuth2 Gateway 中,OAuth2 认证和授权是通过与认证授权服务(通常是 Spring Security OAuth2)进行协作来实现的。当客户端发起请求时,请求会先到达 Gateway,然后 Gateway 会将请求代理到认证授权服务,认证授权服务会对请求进行认证和授权,并返回相应的结果。认证和授权通过后,Gateway 会将请求转发到相应的微服务Spring Cloud OAuth2 Gateway 还提供了可扩展的过滤器机制,可以对请求进行预处理和后处理,比如添加请求头、修改请求内容等。使用过滤器可以实现一些自定义的功能,比如统一鉴权、请求日志记录等。 总结来说,Spring Cloud OAuth2 Gateway 是一个基于 Spring Cloud 的网关服务,用于处理微服务架构下的请求转发和反向代理。它与认证授权服务协作,实现了 OAuth2 认证和授权的功能,并提供了过滤器机制来增强功能和定制化处理。 ### 回答3: Spring Cloud Gateway 是基于Spring WebFlux 提供的一个路由服务,使用它可以轻松实现网关的搭建和管理,并且支持代理请求、路由转发、请求过滤等功能。同时,也提供了 OAuth2 的集成以实现网关级别的身份认证和授权OAuth2 是一种用于开放式授权的协议,它允许用户通过第三方应用或服务授权访问受限资源。Spring Cloud Gateway OAuth2 插件提供了一种在网关中验证和认证用户的方法。 使用 Spring Cloud Gateway OAuth2 插件,可以在网关层面对资源进行保护,只允许通过授权的用户访问受限资源,并且可以根据用户的角色和权限进行进一步的控制。 配置 Spring Cloud Gateway OAuth2 需要以下步骤: 1. 引入相关的依赖,包括 spring-cloud-starter-gateway 和 spring-security-oauth2。 2. 配置 OAuth2 的认证服务器和资源服务器的信息,包括认证服务器的地址、客户端信息和资源服务器的端点等。 3. 在网关的路由配置中,指定需要受保护的路由和相应的过滤器链,例如认证过滤器和授权过滤器。 4. 可以根据需要自定义过滤器,实现一些额外的操作,比如添加请求头、修改请求路径等。 5. 启动网关应用程序。 通过 Spring Cloud Gateway OAuth2 的集成,可以实现网关层面的统一身份认证和权限控制,提高系统的安全性和可扩展性。同时,也简化了微服务中的认证和授权配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值