1

前言

接前面的内容,我们用 spring security 来完成开放接口平台。

授权资源服务

image.png
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>zuul-auth</artifactId>
        <groupId>com.babaznkj.com</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>auth2</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.babaznkj.com</groupId>
            <artifactId>common</artifactId>
        </dependency>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- mybatis启动器 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.starter.version}</version>
        </dependency>

        <!-- alibaba的druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.starter.version}</version>
        </dependency>

        <!-- 不是starter,手动配置 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>
    </dependencies>
</project>

yml

server:
  port: 8082

baba:
  security:
    jwt:
      secret: otherpeopledontknowit
      url: /login
      header: Authorization
      prefix: Bearer
      expiration: 86400
      language: CN

spring:
  application:
    name: backend
  datasource:
    name: test
    url: jdbc:mysql://localhost:3306/baba_icloud_test1?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: carry0610A
    # druid 连接池
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
  main:
    allow-bean-definition-overriding: true # 这个表示允许我们覆盖OAuth2放在容器中的bean对象,一定要配置
  redis:
    host: 192.168.3.119
    port: 6379
    password: 123456

ribbon:
  ReadTimeout: 5000
  SocketTimeout: 5000

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
  instance:
    prefer-ip-address: false
management:
  endpoints:
    security:
      enabled: false
    web:
      exposure:
        include: "*"
mybatis:
  mapper-locations: classpath:mapper/*.xml    # mapper映射文件位置
  type-aliases-package: shuaicj.example.security.backend.entity    # 实体类所在的位置
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  • AuthorizationServerConfiguration.java

    package com.baba.security.auth2.auth;
    
    import com.baba.security.auth2.entity.JdbcTokenStores;
    import com.baba.security.auth2.service.impl.MemberUserDetailsService;
    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.crypto.bcrypt.BCryptPasswordEncoder;
    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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.approval.ApprovalStore;
    import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
    import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
    import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
    import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    
    import javax.sql.DataSource;
    
    /**
     * OAuth 授权服务器配置
     * https://segmentfault.com/a/1190000014371789
     * @author wulongbo
     * @date 2021-11-11
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter  {
    
      @Autowired
      private DataSource dataSource;
      @Autowired
      private MemberUserDetailsService userDetailsService;
      @Autowired
      private AuthenticationManager authenticationManager;
      @Autowired
      private PasswordEncoder passwordEncoder;
    
      //从数据库中查询出客户端信息
      @Bean
      public JdbcClientDetailsService clientDetailsService() {
          JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
          jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
          return jdbcClientDetailsService;
      }
    
      //token保存策略
      @Bean
      public TokenStore tokenStore() {
          return new JdbcTokenStores(dataSource);
      }
    
      //授权信息保存策略
      @Bean
      public ApprovalStore approvalStore() {
          return new JdbcApprovalStore(dataSource);
      }
    
      //授权码模式专用对象
      @Bean
      public AuthorizationCodeServices authorizationCodeServices() {
          return new JdbcAuthorizationCodeServices(dataSource);
      }
    
      //指定客户端登录信息来源
      @Override
      public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
          //从数据库取数据
          clients.withClientDetails(clientDetailsService());
    
    //        // 从内存中取数据
    //        clients.inMemory()
    //                .withClient("baidu")
    //                .secret(passwordEncoder.encode("12345"))
    //                .resourceIds("mayikt_resource")
    //                .authorizedGrantTypes(
    //                        "authorization_code",
    //                        "password",
    //                        "client_credentials",
    //                        "implicit",
    //                        "refresh_token"
    //                )// 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials
    ////                .scopes("read", "write")// 允许的授权范围
    //                .scopes("all")// 允许的授权范围
    //                .autoApprove(false)
    //                //加上验证回调地址
    //                .redirectUris("http://www.baidu.com");
    //        // 读数据库
    //        clients.inMemory()
    //                // appid
    //                .withClient("mayikt_appid1")
    //                .secret(passwordEncoder.encode("1234567"))
    //                // 授权码
    //                .authorizedGrantTypes("authorization_code")
    //                // 作用域 表示所有的接口都可以访问 分配我们的appid 调用接口的权限
    //                .scopes("all")
    //                .resourceIds("mayikt_resource")
    //                // 用户选择授权之后,跳转到该地址传递code授权码
    //                .redirectUris("http://www.mayikt.com/callback");
      }
    
      //检测token的策略
      @Override
      public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
          oauthServer.allowFormAuthenticationForClients()    //允许form表单客户端认证,允许客户端使用client_id和client_secret获取token
    //                .checkTokenAccess("isAuthenticated()")     //通过验证返回token信息
                  .checkTokenAccess("permitAll()")     //打开check_token
                  .tokenKeyAccess("permitAll()")         // 获取token请求不进行拦截
                  .passwordEncoder(passwordEncoder);
      }
    
      //OAuth2的主配置信息
      @Override
      public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
          endpoints
                  .approvalStore(approvalStore())
                  .authenticationManager(authenticationManager)
                  .authorizationCodeServices(authorizationCodeServices())
                  .tokenStore(tokenStore())
                  .userDetailsService(userDetailsService);
      }
    
      @Bean
      public PasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder();
      }
    
    }
    
  • BaseClientDetailService.java

    package com.baba.security.auth2.auth;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.oauth2.provider.ClientDetails;
    import org.springframework.security.oauth2.provider.ClientDetailsService;
    import org.springframework.security.oauth2.provider.ClientRegistrationException;
    import org.springframework.security.oauth2.provider.NoSuchClientException;
    import org.springframework.security.oauth2.provider.client.BaseClientDetails;
    
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 自定义客户端认证
     * @author wulongbo
     * @date 2021-11-11
     */
    public class BaseClientDetailService implements ClientDetailsService {
    
      private static final Logger log = LoggerFactory.getLogger(BaseClientDetailService.class);
    
      @Override
      public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
          System.out.println(clientId);
          BaseClientDetails client = null;
          //这里可以改为查询数据库
          if("client".equals(clientId)) {
              log.info(clientId);
              client = new BaseClientDetails();
              client.setClientId(clientId);
              client.setClientSecret("{noop}123456");
              //client.setResourceIds(Arrays.asList("order"));
              client.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
                      "client_credentials", "refresh_token", "password", "implicit"));
              //不同的client可以通过 一个scope 对应 权限集
              client.setScope(Arrays.asList("all", "select"));
              client.setAuthorities(AuthorityUtils.createAuthorityList("admin_role"));
              client.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); //1天
              client.setRefreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)); //1天
              Set<String> uris = new HashSet<>();
              uris.add("http://localhost:8080/auth");
              client.setRegisteredRedirectUri(uris);
          }
          if(client == null) {
              throw new NoSuchClientException("No client width requested id: " + clientId);
          }
          return client;
      }
    
    }
    
  • ResourceServerConfiguration.java

    package com.baba.security.auth2.auth;
    
    import com.baba.security.auth2.entity.JdbcTokenStores;
    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.http.HttpMethod;
    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;
    import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    
    import javax.sql.DataSource;
    
    /**
     * OAuth 资源服务器配置
     *
     * @author wulongbo
     * @date 2021-11-11
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    
      @Autowired
      private DataSource dataSource;
      @Autowired
      private TokenStore tokenStore;
    
      /**
       * 指定token的持久化策略
       * 其下有   RedisTokenStore保存到redis中,
       * JdbcTokenStore保存到数据库中,
       * InMemoryTokenStore保存到内存中等实现类,
       * 这里我们选择保存在数据库中
       *
       * @return
       */
      @Bean
      public TokenStore jdbcTokenStore() {
          return new JdbcTokenStores(dataSource);
      }
    
      @Override
      public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
          resources.resourceId("mayikt_resource")//指定当前资源的id,非常重要!必须写!
                  .tokenStore(jdbcTokenStore());
    //                .tokenStore(tokenStore);//指定保存token的方式
      }
    
    //    @Primary
    //    @Bean
    //    public RemoteTokenServices remoteTokenServices() {
    //        final RemoteTokenServices tokenServices = new RemoteTokenServices();
    //        tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
    //        return tokenServices;
    //    }
    
      @Override
      public void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests()
                  //指定不同请求方式访问资源所需要的权限,一般查询是read,其余是write。
                  .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                  .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                  .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                  .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                  .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
                  .and()
                  .headers().addHeaderWriter((request, response) -> {
              response.addHeader("Access-Control-Allow-Origin", "*");//允许跨域
              if (request.getMethod().equals("OPTIONS")) {//如果是跨域的预检请求,则原封不动向下传达请求头信息
                  response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
                  response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
              }
          });
      }
    
    }
    
  • SecurityConfig.java

    package com.baba.security.auth2.auth;
    
    import com.baba.security.auth2.service.impl.MemberUserDetailsService;
    import com.baba.security.common.utils.MD5Util;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    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;
    import org.springframework.stereotype.Component;
    
    /**
     * 配置我们httpBasic 登陆账号和密码
     */
    @Component
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Autowired
      private MemberUserDetailsService memberUserDetailsService;
    
      @Bean
      public PasswordEncoder passwordEncoder() {
          return new BCryptPasswordEncoder();
      }
    
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //        auth.
    //                inMemoryAuthentication()
    //                .withUser("mayikt")
    //                .password(passwordEncoder().encode("654321"))
    //                .authorities("/*");
    //
    //        auth.
    //                inMemoryAuthentication()
    //                .withUser("baidu")
    //                .password(passwordEncoder().encode("54321"))
    //                .authorities("/*");
    //        System.out.println("===============================");
    //        System.out.println(passwordEncoder().encode("123456"));
    //        System.out.println(new BCryptPasswordEncoder().encode("12345"));
          auth.userDetailsService(memberUserDetailsService).passwordEncoder(new PasswordEncoder() {
              /**
               * 对密码MD5
               * @param rawPassword
               * @return
               */
              @Override
              public String encode(CharSequence rawPassword) {
                  return MD5Util.encode((String) rawPassword);
              }
    
              /**
               * rawPassword 用户输入的密码
               * encodedPassword 数据库DB的密码
               * @param rawPassword
               * @param encodedPassword
               * @return
               */
              @Override
              public boolean matches(CharSequence rawPassword, String encodedPassword) {
                  String rawPass = MD5Util.encode((String) rawPassword);
                  boolean result = rawPass.equals(encodedPassword);
                  return result;
              }
          });
    
      }
    
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests()
                  .anyRequest().authenticated() //所有请求都需要通过认证
                  .antMatchers("/auth2/**").permitAll()
                  .and()
                  .httpBasic() //Basic登录
                  .and()
                  .csrf().disable(); //关跨域保护
      }
    
      @Override
      @Bean
      public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
      }
    }
  • ProductController.java

    package com.baba.security.auth2.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @Author wulongbo
     * @Date 2021/11/11 19:10
     * @Version 1.0
     */
    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
      @RequestMapping("/findAll")
      public String findAll() {
          return "授权成功";
      }
    
      @RequestMapping("/findAll1")
      public String findAll1() {
          return "授权成功1";
      }
    }
    
  • dao和service以及mapper.xml参考前面的就好了
  • JdbcTokenStores.java

    package com.baba.security.auth2.entity;
    
    import com.alibaba.druid.support.logging.Log;
    import com.alibaba.druid.support.logging.LogFactory;
    import org.springframework.dao.EmptyResultDataAccessException;
    import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
    import org.springframework.security.oauth2.common.OAuth2AccessToken;
    import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
    
    import javax.sql.DataSource;
    
    /**
     * @Author wulongbo
     * @Date 2021/11/11 19:56
     * @Version 1.0
     */
    public class JdbcTokenStores extends JdbcTokenStore {
    
      private static final Log LOG = LogFactory.getLog(JdbcTokenStores.class);
      public JdbcTokenStores(DataSource dataSource) {
          super(dataSource);
      }
      @Override
      public OAuth2AccessToken readAccessToken(String tokenValue) {
          OAuth2AccessToken accessToken = null;
    
          try {
              accessToken = new DefaultOAuth2AccessToken(tokenValue);
          }
          catch (EmptyResultDataAccessException e) {
              if (LOG.isInfoEnabled()) {
                  LOG.info("Failed to find access token for token "+tokenValue);
              }
          }
          catch (IllegalArgumentException e) {
              LOG.warn("Failed to deserialize access token for " +tokenValue,e);
              removeAccessToken(tokenValue);
          }
    
          return accessToken;
      }
    }
    
  • Application.java

    package com.baba.security.auth2;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * Spring boot app.
     *
     * @author wulongbo 2021/10/18
     */
    @SpringBootApplication
    @MapperScan("com.baba.security.auth2.dao")
    public class Application {
    
      public static void main(String[] args) {
    
          SpringApplication.run(Application.class, args);
      }
    }
    
  • 数据库image.png
INSERT INTO `baba_icloud_test1`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('baidu', 'mayikt_resource,user,device', '$2a$10$32N/ptu3jY0WjFH0qLbcEO2ZcCg4gYCJvMbwmqzf84qNCcDFBLl4q', 'read,write', 'authorization_code', 'http://www.mayikt.com/callback', NULL, NULL, NULL, NULL, NULL);
INSERT INTO `baba_icloud_test1`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('mayikt_appid', 'mayikt_resource', '$2a$10$6/Sab9Au.CdKqyE4x0gr0OJZrzrcDCWZ9GLqMDF6KX.jHad5vlkeO', 'read,write,trust', 'authorization_code', 'http://www.mayikt.com/callback', NULL, NULL, NULL, NULL, NULL);
  • 测试

访问http://localhost:8082/oauth/authorize?client_id=mayikt_appid&response_type=code
image.png获取到code授权码
image.png根据授权码获取access_token
http://localhost:8082/oauth/token?code=StPCWn&grant_type=authorization_code&redirect_uri=http://www.mayikt.com/callback&Scope=all&client_id=mayikt_appid&client_secret=123456
image.png
当然也可以通过网关来访问:
http://localhost:8080/auth2/oauth/token?code=n6d6hP&grant_type=authorization_code&redirect_uri=http://www.mayikt.com/callback&Scope=all&client_id=mayikt_appid&client_secret=123456
检查access_token
http://localhost:8082/oauth/check_token?token=5c469473-8599-44f2-9061-fbe61be37ff5image.png
调用接口http://localhost:8080/auth2/product/findAll主装Bearer拼接token放入header中请求接口
image.png
当然也可以不组装直接请求
image.png


isWulongbo
228 声望26 粉丝

在人生的头三十年,你培养习惯,后三十年,习惯铸就你