美文网首页Spring Boot&Spring CloudSpring Cloud Spring Cloud
SpringCloud(七):搭建OAuth2认证授权服务

SpringCloud(七):搭建OAuth2认证授权服务

作者: 抓蛙行者 | 来源:发表于2017-11-06 17:12 被阅读1526次

    一、概念部分

    1.为什么需要做一个单独的认证授权服务?

    为了保证服务对外的安全性,往往都会在服务接口采用权限校验机制,为了防止客户端在发起请求中途被篡改数据等安全方面的考虑,还会有一些签名校验的机制。
    在分布式微服务架构的系统中,我们把原本复杂的系统业务拆分成了若干个独立的微服务应用,我们不得不在每个微服务中都实现这样一套校验逻辑,这样就会有很多的代码和功能冗余,随着服务的扩大和业务需求的复杂度不断变化,修改校验逻辑变得相当麻烦,一处改,处处改。所以我们需要把认证授权服务单独出来,做成一个服务进行调用。

    2.授权服务的使用场景有哪些?

    授权服务并不是每个应用的接口直接去调用,判断哪些用户有权限访问接口。 而是通过API网关进行统一调用。用户所有的请求都必须先通过API网关,API网关在进行路由转发之前对该请求进行前置校验,我们可以方便的使用OAuth2认证授权服务来做单点登录等操作。
    可以使用OAuth2来实现对多个服务的统一认证授权。

    3.本项目的主要操作流程

    关于OAuth2的协议此处不再介绍,本项目演示主要的认证和授权步骤,简单来说就是客户端根据约定的ClientID、ClientSecret、Scope来从Access Token URL地址获取AccessToken,并经过AuthURL认证,用得到的AccessToken来访问其他资源接口。

    项目结构如下,其中核心部分为core包下的config包中的三个配置类
    从上往下分别是是授权服务配置,资源服务配置和安全配置


    二、代码示例

    1.新建一个SpringBoot项目 auth-server,在项目中添加依赖
    build.gradle

    dependencies {
       compile('org.springframework.cloud:spring-cloud-starter-oauth2')
       compile('org.springframework.cloud:spring-cloud-starter-security')
       compile('org.springframework.boot:spring-boot-starter-data-mongodb')
       compile('org.springframework.boot:spring-boot-starter-data-redis')
       compile('org.springframework.cloud:spring-cloud-starter-eureka')
       testCompile('org.springframework.boot:spring-boot-starter-test')
    }
    

    2.新建配置文件 application.yml

    #服务器配置
    server:
      #端口
      port: 8020
    
    #服务器发现注册配置
    eureka:
      client:
        serviceUrl:
          #配置服务中心(可配置多个,用逗号隔开)
          defaultZone: https://www.apiboot.cn/eureka
    
    #spring配置
    spring:
      #应用配置
      application:
        #名称: OAuth2认证授权服务
        name: auth-server
      #数据库配置
      data:
        mongodb:
          port: 27017
          database: auth_server
    
    #安全配置
    security:
      #oauth2配置
      oauth2:
        resource:
          filter-order: 3
    

    3.应用启动类添加注解

    /**
    * OAuth2认证授权服务
    * @ EnableDiscoveryClient 启用服务注册发现
    */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class AuthServerApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(AuthServerApplication.class, args);
       }
    
    }
    

    4.新建账户实体类 Account.java

    /**
    * 账户实体类
    */
    public class Account {
        @Id
        private String id;          // 主键
        private String userName;    // 用户名
        private String passWord;    // 密码
        private String[] roles;     // 角色
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getPassWord() {
            return passWord;
        }
    
        public void setPassWord(String passWord) {
            this.passWord = passWord;
        }
    
        public String[] getRoles() {
            return roles;
        }
    
        public void setRoles(String[] roles) {
            this.roles = roles;
        }
    }
    

    5.新建账户表数据操作类,MongoDB操作接口 AccountRepository.java

    /**
    * 账户数据库操作类
    * MongoDB操作接口
    */
    @Component
    public interface AccountRepository extends MongoRepository<Account, String> {
    
        /**
         * 根据用户名查找账户信息
         * @param username 用户名
         * @return 账户信息
         */
        Account findByUserName(String username);
    }
    

    6.新建用户信息控制器 UserController.java

    /**
    *  用户信息控制器
    */
    @RestController
    public class UserController {
    
        @Autowired
        private AccountRepository accountRepository;    // 账户数据操作
    
        /**
         * 初始化用户数据
         */
        @Autowired
        public void init(){
    
            // 为了方便测试,这里添加了两个不同角色的账户
            accountRepository.deleteAll();
    
            Account accountA = new Account();
            accountA.setUserName("admin");
            accountA.setPassWord("admin");
            accountA.setRoles(new String[]{"ROLE_ADMIN","ROLE_USER"});
            accountRepository.save(accountA);
    
            Account accountB = new Account();
            accountB.setUserName("guest");
            accountB.setPassWord("pass123");
            accountB.setRoles(new String[]{"ROLE_GUEST"});
            accountRepository.save(accountB);
        }
    
        /**
         * 获取授权用户的信息
         * @param user 当前用户
         * @return 授权信息
         */
        @GetMapping("/user")
        public Principal user(Principal user){
            return user;
        }
    }
    

    7.新建用户信息服务类,实现 Spring Security的UserDetailsService接口方法,用于身份认证 DomainUserDetailsService.java

    /**
    * 用户信息服务
    * 实现 Spring Security的UserDetailsService接口方法,用于身份认证
    */
    @Service
    public class DomainUserDetailsService implements UserDetailsService {
    
        @Autowired
        private AccountRepository accountRepository;    // 账户数据操作接口
    
        /**
         * 根据用户名查找账户信息并返回用户信息实体
         * @param username 用户名
         * @return 用于身份认证的 UserDetails 用户信息实体
         * @throws UsernameNotFoundException
         */
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            Account account = accountRepository.findByUserName(username);
            if (account!=null){
                return new User(account.getUserName(),account.getPassWord(), AuthorityUtils.createAuthorityList(account.getRoles()));
            }else {
                throw  new UsernameNotFoundException("用户["+username+"]不存在");
            }
        }
    }
    

    8.新建授权服务配置类,继承AuthorizationServerConfigurerAdapter
    AuthorizationServerConfig.java

    /**
    * 授权服务器配置
    * @ EnableAuthorizationServer 启用授权服务
    */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private AuthenticationManager authenticationManager;    // 认证管理器
    
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;  // redis连接工厂
    
        /**
         * 令牌存储
         * @return redis令牌存储对象
         */
        @Bean
        public TokenStore tokenStore() {
            return new RedisTokenStore(redisConnectionFactory);
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(this.authenticationManager);
            endpoints.tokenStore(tokenStore());
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security
                    .tokenKeyAccess("permitAll()")
                    .checkTokenAccess("isAuthenticated()");
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("android")
                    .scopes("xx")
                    .secret("android")
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                    .and()
                    .withClient("webapp")
                    .scopes("xx")
                    .authorizedGrantTypes("implicit");
        }
    
    }
    

    9.新建资源服务配置类,继承ResourceServerConfigurerAdapter
    ResourceServerConfig.java

    /**
    * 资源服务配置
    * @ EnableResourceServer 启用资源服务
    */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.requestMatcher(new OAuth2RequestedMatcher())
                    .authorizeRequests()
                    .antMatchers(HttpMethod.OPTIONS).permitAll()
                    .anyRequest().authenticated();
        }
    
        /**
         * 定义OAuth2请求匹配器
         */
        private static class OAuth2RequestedMatcher implements RequestMatcher {
            @Override
            public boolean matches(HttpServletRequest request) {
                String auth = request.getHeader("Authorization");
                //判断来源请求是否包含oauth2授权信息,这里授权信息来源可能是头部的Authorization值以Bearer开头,或者是请求参数中包含access_token参数,满足其中一个则匹配成功
                boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
                boolean haveAccessToken = request.getParameter("access_token")!=null;
                return haveOauth2Token || haveAccessToken;
            }
        }
       
    }
    

    10.新建安全配置类,继承WebSecurityConfigurerAdapter
    SecurityConfig.java

    /**
    * 安全配置
    * @ EnableWebSecurity 启用web安全配置
    * @ EnableGlobalMethodSecurity 启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
    */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 注入用户信息服务
         * @return 用户信息服务对象
         */
        @Bean
        public UserDetailsService userDetailsService() {
            return new DomainUserDetailsService();
        }
    
        /**
         * 全局用户信息
         * @param auth 认证管理
         * @throws Exception 用户认证异常信息
         */
        @Autowired
        public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService());
        }
    
        /**
         * 认证管理
         * @return 认证管理对象
         * @throws Exception 认证异常信息
         */
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /**
         * http安全配置
         * @param http http安全对象
         * @throws Exception http安全异常信息
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and()
                    .httpBasic().and().csrf().disable();
        }
    
    }
    

    三、演示

    假设我们现在要直接访问 /user接口,我们打开PoatMan直接请求该接口



    发现返回了401状态码,并且报了Unauthorized 未经授权的错误,提示信息显示 访问此资源需要完全身份验证。 添加了Spring Security+OAuth2后,所有的资源访问都需要通过token

    1.获取access_token

    启动服务,打开PostMan切换到Authorization页卡,Type类型选择Basic Auth,Username和Password填写授权服务配置中对应的withClient和secret的值,这里都写android

    点击Update Request,切换到Headers页卡,发现请求头里多了个Authorization参数,参数值就是根据Authorization页卡填写的授权信息生成的,要获取token必须有该参数值


    使用post方法访问授权服务的 /oauth/token地址,post参数需要填写grant_type、username、password。点击send请求,将会返回如下access_token信息。



    拿到access_token后,就可以在请求其他资源接口的时候携带上该token参数值获取该角色可获取的资源,打开浏览器访问 /user接口并携带上 access_token参数值

    2.使用正确的姿势获取access_token,并根据access_token获取资源

    切换到Authorization页卡,选择OAuth2.0,点击Get New Access Token


    TokenName可以随意填写,其他信息根据实际情况填写。点击Request Token后,将会跳出输入用户名和密码的页面(这个操作其实就是根据用户名和密码登录并获取AccessToken)



    登录成功后,我们看到左侧有个我们刚刚新建的auth,点击auth,右侧会显示该请求获取到的AccessToken信息。点击UseToken


    点击Use Token后,发现请求头Headers页卡里添加了Authorization的参数值



    点击Send,请求/user接口,正常返回用户授权信息


    到此 OAuth2认证授权服务已经搭建完毕了,关于api-gateway和OAuth2认证授权服务的整合调用,会在下一篇文章中写到,敬请期待。
    项目地址:https://github.com/lanshiqin/cloud-project
    欢迎点赞

    相关文章

      网友评论

      • 惊破霓裳羽衣曲:点击Request Token后 弹出 login page 此处登陆失败
      • 王伟_BOCS_昆山:大神你好,我有一些不明白的,恳请指导:用password方式获取token时如何判断传入的用户名密码是否正确的?项目中是直接从UserDetailsService实现中获取的,但是没有判断。
      • Nathans:gateway和oauth还有RS结合呢?
        抓蛙行者:@Nathans 哈哈,很感谢关注我的文章,我也想写一篇。不过能力和精力有限,你可以去看看其他作者的优秀文章,这里我没有使用jwt,而是使用redis做令牌存储,至于sso如果有时间我会写一篇具体业务的单点登录。下一篇文章我只简单的演示通过api-gateway从服务注册中心获取OAuth2认证授权服务得到token,并且根据token去调用其他微服务的资源,zuul+OAuth2+DS去调用资源的示例。
        Nathans:@蓝士钦 坐等一篇oauth + zuul + jwt+ sso呵呵
        抓蛙行者:@Nathans 这里为了减少篇幅,会在下一篇文章写到

      本文标题:SpringCloud(七):搭建OAuth2认证授权服务

      本文链接:https://www.haomeiwen.com/subject/afwlmxtx.html