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());
    }

    /**
     * 使用非对称加密算法来对Token进行签名
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {

        final JwtAccessTokenConverter converter = new JwtAccessToken();
        // 导入证书
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));

        return 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 {

    /**
     * 生成token
     * @param accessToken
     * @param authentication
     * @return
     */
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);

        // 设置额外用户信息
        BaseUser baseUser = ((BaseUserDetail) authentication.getPrincipal()).getBaseUser();
        baseUser.setPassword(null);
        // 将用户信息添加到token额外信息中
        defaultOAuth2AccessToken.getAdditionalInformation().put(Constant.USER_INFO, baseUser);

        return super.enhance(defaultOAuth2AccessToken, authentication);
    }

    /**
     * 解析token
     * @param value
     * @param map
     * @return
     */
    @Override
    public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map){
        OAuth2AccessToken oauth2AccessToken = super.extractAccessToken(value, map);
        convertData(oauth2AccessToken, oauth2AccessToken.getAdditionalInformation());
        return oauth2AccessToken;
    }

    private void convertData(OAuth2AccessToken accessToken,  Map<String, ?> map) {
        accessToken.getAdditionalInformation().put(Constant.USER_INFO,convertUserData(map.get(Constant.USER_INFO)));

    }

    private BaseUser convertUserData(Object map) {
        String json = JsonUtils.deserializer(map);
        BaseUser user = JsonUtils.serializable(json, BaseUser.class);
        return 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 {

    private final BaseUser baseUser;
    private final org.springframework.security.core.userdetails.User user;

    public BaseUserDetail(BaseUser baseUser, User user) {
        this.baseUser = baseUser;
        this.user = user;
    }

    @Override
    public void eraseCredentials() {
        user.eraseCredentials();
    }

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

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

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

    @Override
    public boolean isAccountNonExpired() {
        return user.isAccountNonExpired();
    }

    @Override
    public boolean isAccountNonLocked() {
        return user.isAccountNonLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return user.isCredentialsNonExpired();
    }

    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }

    public BaseUser getBaseUser() {
        return 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 {

    // 自动注入UserDetailsService
    @Autowired
    private BaseUserDetailService baseUserDetailService;

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

    /**
     * 用户验证
     * @param auth
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // 设置userDetailsService
        provider.setUserDetailsService(baseUserDetailService);
        // 禁止隐藏用户未找到异常
        provider.setHideUserNotFoundExceptions(false);
        // 使用BCrypt进行密码的hash
        provider.setPasswordEncoder(new BCryptPasswordEncoder(6));
        return provider;
    }
}

UserDetailsService 实现

@Service
public class BaseUserDetailService implements UserDetailsService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private BaseUserService baseUserService;
    @Autowired
    private BaseRoleService baseRoleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

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

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

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

        });

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

    private boolean isActive(int active){
        return active == 1 ? true : false;
    }
}

授权服务器验证

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编码,返回:

原文地址:https://www.cnblogs.com/fp2952/p/8973613.html

时间: 2024-10-28 20:29:34

Spring Cloud OAuth2(一) 搭建授权服务的相关文章

【译文】用Spring Cloud和Docker搭建微服务平台

by Kenny Bastani Sunday, July 12, 2015 转自:http://www.kennybastani.com/2015/07/spring-cloud-docker-microservices.html This blog series will introduce you to some of the foundational concepts of building a microservice-based platform using Spring Cloud

手把手spring cloud整合zookeeper搭建微服务

环境配置:JDK8 +IDEA 开发工具 **说明:下面搭建过程基于你本地已经配置好zookeeper 一.先搭建父工程(PS:是可以不搭建父工程的,即每个服务和client都是单独的spring boot项目,也是OK的,搭建父工程纯粹为了将项目放在一起,且pom文件可以少一点而已!) 1.先搭建父工程:spring initial -- 第二步,Type改成Maven Pom,Artifact改成demo-parent,便于理解 -- 引入两个依赖,web和zookeeper-discove

跟着园内spring cloud+.net core搭建微服务架构 服务消费出错问题

http://www.cnblogs.com/longxianghui/p/7561259.html 最近在跟随着园区内的这个博客做服务发现的时候,发觉在vs 上调整了端口后还是无法实现通过,order 服务访问product,一访问 就抛出错误.经过近乎两周的时间 发现,通过 cmd 命令行启动后,可以实现服务消费 .  当然 ,也可按照评论所说的发布到iis上,我没这么发布处理,一来我是觉得不够方便快捷,二来,我是希望脱离iis 运行的 如图:不过在启动前,需要把配置文件拷贝到生成了dll

Spring Cloud 微服务中搭建 OAuth2.0 认证授权服务

在使用 Spring Cloud 体系来构建微服务的过程中,用户请求是通过网关(ZUUL 或 Spring APIGateway)以 HTTP 协议来传输信息,API 网关将自己注册为 Eureka 服务治理下的应用,同时也从 Eureka 服务中获取所有其他微服务的实例信息.搭建 OAuth2 认证授权服务,并不是给每个微服务调用,而是通过 API 网关进行统一调用来对网关后的微服务做前置过滤,所有的请求都必须先通过 API 网关,API 网关在进行路由转发之前对该请求进行前置校验,实现对微服

使用 Spring Cloud 和 Docker 构建微服务架构

如何使用Spring Boot.Spring Cloud.Docker和Netflix的一些开源工具来构建一个微服务架构. 本文通过使用Spring Boot.Spring Cloud和Docker构建的概念型应用示例,提供了了解常见的微服务架构模式的起点. 该代码可以在Github上获得,并且在Docker Hub上提供了镜像.您只需要一个命令即可启动整个系统. 我选择了一个老项目作为这个系统的基础,它的后端以前是单一应用.此应用提供了处理个人财务.整理收入开销.管理储蓄.分析统计和创建简单预

从 Spring Cloud 开始,聊聊微服务架构实践之路

[编者的话]随着公司业务量的飞速发展,平台面临的挑战已经远远大于业务,需求量不断增加,技术人员数量增加,面临的复杂度也大大增加.在这个背景下,平台的技术架构也完成了从传统的单体应用到微服务化的演进. 系统架构的演进过程 单一应用架构(第一代架构) 这是平台最开始的情况,当时流量小,为了节约成本,并将所有应用都打包放到一个应用里面,采用的架构为 .NET SQL Server: 表示层:位于最外层(最上层),最接近用户.用于显示数据和接收用户输入的数 据,为用户提供一种交互式操作的界面,平台所使用

spring cloud 入门系列八:使用spring cloud sleuth整合zipkin进行服务链路追踪

好久没有写博客了,主要是最近有些忙,今天忙里偷闲来一篇. =======我是华丽的分割线========== 微服务架构是一种分布式架构,微服务系统按照业务划分服务单元,一个微服务往往会有很多个服务单元,一个请求往往会有很多个单元参与,一旦请求出现异常,想要去定位问题点真心不容易,因此需要有个东西去跟踪请求链路,记录一个请求都调用了哪些服务单元,调用顺序是怎么样的以及在各个服务单元处理的时间长短.常见的服务链路追踪组件有google的dapper.twitter的zipkin.阿里的鹰眼等,它们

spring cloud 2.x版本 Ribbon服务发现教程(内含集成Hystrix熔断机制)

本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 前言 本文基于前两篇文章eureka-server和eureka-client的实现. 参考 eureka-server eureka-client 1 Ribbon工程搭建 1.1 创建spring boot工程:eureka-ribbon 1.2 pom.xml所需要依赖的jar包 <dependency> <groupId>org.springframework.clo

Spring Cloud官方文档中文版-服务发现:Eureka服务端

官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR3/#spring-cloud-eureka-server 文中例子我做了一些测试在:http://git.oschina.net/dreamingodd/spring-cloud-preparation Service Discovery: Eureka Server 服务发现:Eureka服务端 How to Include Eureka Server 如何创建Eurek

使用Spring Cloud和Docker构建微服务

什么是Spring Cloud?Spring Cloud 是Pivotal提 供的用于简化分布式系统构建的工具集.Spring Cloud引入了云平台连接器(Cloud Connector)和服务连接器(Service Connector)的概念.云平台连接器是一个接口,需要由云平台提供者进行实现,以便库中的其他模块可以与该云平台协同工作.(更多介绍,可以阅读 InfoQ的这篇文章:http://www.infoq.com/cn/news/2014/06/spring-cloud-platfor