Sfoglia il codice sorgente

Merge pull request #3 from yuuki80code/master

加入Client管理
mrbird 6 anni fa
parent
commit
0a649785cc

+ 4 - 0
febs-auth/pom.xml

@@ -44,6 +44,10 @@
             <artifactId>logstash-logback-encoder</artifactId>
             <version>6.1</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 57 - 22
febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsAuthorizationServerConfigurer.java

@@ -3,12 +3,16 @@ package cc.mrbird.febs.auth.configure;
 import cc.mrbird.febs.auth.properties.FebsAuthProperties;
 import cc.mrbird.febs.auth.properties.FebsClientsProperties;
 import cc.mrbird.febs.auth.service.FebsUserDetailService;
+import cc.mrbird.febs.auth.service.RedisClientDetailsService;
 import cc.mrbird.febs.auth.translator.FebsWebResponseExceptionTranslator;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 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.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
@@ -17,10 +21,14 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.A
 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.provider.token.DefaultAccessTokenConverter;
+import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
 import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
 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.util.UUID;
 
 /**
  * 认证服务器配置
@@ -41,43 +49,60 @@ public class FebsAuthorizationServerConfigurer extends AuthorizationServerConfig
     private FebsWebResponseExceptionTranslator exceptionTranslator;
     @Autowired
     private FebsAuthProperties properties;
+    @Autowired
+    private RedisClientDetailsService redisClientDetailsService;
+    @Autowired
+    private RedisConnectionFactory redisConnectionFactory;
 
     @Override
     public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
-        FebsClientsProperties[] clientsArray = properties.getClients();
-        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
-        if (ArrayUtils.isNotEmpty(clientsArray)) {
-            for (FebsClientsProperties client : clientsArray) {
-                if (StringUtils.isBlank(client.getClient())) {
-                    throw new Exception("client不能为空");
-                }
-                if (StringUtils.isBlank(client.getSecret())) {
-                    throw new Exception("secret不能为空");
-                }
-                String[] grantTypes = StringUtils.splitByWholeSeparatorPreserveAllTokens(client.getGrantType(), ",");
-                builder.withClient(client.getClient())
-                        .secret(passwordEncoder.encode(client.getSecret()))
-                        .authorizedGrantTypes(grantTypes)
-                        .accessTokenValiditySeconds(client.getAccessTokenValiditySeconds())
-                        .refreshTokenValiditySeconds(client.getRefreshTokenValiditySeconds())
-                        .scopes(client.getScope());
-            }
-        }
+//        FebsClientsProperties[] clientsArray = properties.getClients();
+//        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
+//        if (ArrayUtils.isNotEmpty(clientsArray)) {
+//            for (FebsClientsProperties client : clientsArray) {
+//                if (StringUtils.isBlank(client.getClient())) {
+//                    throw new Exception("client不能为空");
+//                }
+//                if (StringUtils.isBlank(client.getSecret())) {
+//                    throw new Exception("secret不能为空");
+//                }
+//                String[] grantTypes = StringUtils.splitByWholeSeparatorPreserveAllTokens(client.getGrantType(), ",");
+//                builder.withClient(client.getClient())
+//                        .secret(passwordEncoder.encode(client.getSecret()))
+//                        .authorizedGrantTypes(grantTypes)
+//                        .accessTokenValiditySeconds(client.getAccessTokenValiditySeconds())
+//                        .refreshTokenValiditySeconds(client.getRefreshTokenValiditySeconds())
+//                        .scopes(client.getScope());
+//            }
+//        }
+        clients.withClientDetails(redisClientDetailsService);
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
-        endpoints.tokenStore(jwtTokenStore())
+        endpoints.tokenStore(tokenStore())
                 .accessTokenConverter(jwtAccessTokenConverter())
                 .userDetailsService(userDetailService)
                 .authenticationManager(authenticationManager)
                 .exceptionTranslator(exceptionTranslator);
+        if(properties.getEnableJwt()){
+            endpoints.accessTokenConverter(jwtAccessTokenConverter());
+        }else {
+            endpoints.tokenServices(defaultTokenServices());
+        }
     }
 
     @Bean
-    public TokenStore jwtTokenStore() {
-        return new JwtTokenStore(jwtAccessTokenConverter());
+    public TokenStore tokenStore() {
+        if(properties.getEnableJwt()){
+            return new JwtTokenStore(jwtAccessTokenConverter());
+        }else {
+            RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
+            // 解决每次生成的的token都一样的问题
+            redisTokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
+            return redisTokenStore;
+        }
     }
 
     @Bean
@@ -91,4 +116,14 @@ public class FebsAuthorizationServerConfigurer extends AuthorizationServerConfig
         return accessTokenConverter;
     }
 
+    @Primary
+    @Bean
+    public DefaultTokenServices defaultTokenServices() {
+        DefaultTokenServices tokenServices = new DefaultTokenServices();
+
+        tokenServices.setTokenStore(tokenStore());
+        tokenServices.setSupportRefreshToken(true);
+        tokenServices.setClientDetailsService(redisClientDetailsService);
+        return tokenServices;
+    }
 }

+ 77 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/controller/OauthCliendetailsController.java

@@ -0,0 +1,77 @@
+package cc.mrbird.febs.auth.controller;
+
+import cc.mrbird.febs.auth.service.IOauthCliendetailsService;
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.entity.QueryRequest;
+import cc.mrbird.febs.common.entity.auth.OauthCliendetails;
+import cc.mrbird.febs.common.exception.FebsException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.Map;
+
+import static cc.mrbird.febs.common.utils.FebsUtil.getDataTable;
+
+/**
+ *  Controller
+ *
+ * @author MrBird
+ * @date 2019-09-09 14:13:23
+ */
+@Slf4j
+@Validated
+@RestController
+@RequestMapping("client")
+public class OauthCliendetailsController {
+
+    @Autowired
+    private IOauthCliendetailsService oauthCliendetailsService;
+
+
+    @GetMapping
+    @PreAuthorize("hasAnyAuthority('client:view')")
+    public FebsResponse oauthCliendetailsList(QueryRequest request, OauthCliendetails oauthCliendetails) {
+        Map<String, Object> dataTable = getDataTable(this.oauthCliendetailsService.findOauthCliendetailss(request, oauthCliendetails));
+        return new FebsResponse().data(dataTable);
+    }
+
+    @PostMapping
+    @PreAuthorize("hasAnyAuthority('client:add')")
+    public void addOauthCliendetails(@Valid OauthCliendetails oauthCliendetails) throws FebsException {
+        try {
+            this.oauthCliendetailsService.createOauthCliendetails(oauthCliendetails);
+        } catch (Exception e) {
+            String message = "新增OauthCliendetails失败";
+            log.error(message, e);
+            throw new FebsException(message);
+        }
+    }
+
+    @DeleteMapping
+    @PreAuthorize("hasAnyAuthority('client:delete')")
+    public void deleteOauthCliendetails(OauthCliendetails oauthCliendetails) throws FebsException {
+        try {
+            this.oauthCliendetailsService.deleteOauthCliendetails(oauthCliendetails);
+        } catch (Exception e) {
+            String message = "删除OauthCliendetails失败";
+            log.error(message, e);
+            throw new FebsException(message);
+        }
+    }
+
+    @PutMapping
+    @PreAuthorize("hasAnyAuthority('client:update')")
+    public void updateOauthCliendetails(OauthCliendetails oauthCliendetails) throws FebsException {
+        try {
+            this.oauthCliendetailsService.updateOauthCliendetails(oauthCliendetails);
+        } catch (Exception e) {
+            String message = "修改OauthCliendetails失败";
+            log.error(message, e);
+            throw new FebsException(message);
+        }
+    }
+}

+ 14 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/OauthCliendetailsMapper.java

@@ -0,0 +1,14 @@
+package cc.mrbird.febs.auth.mapper;
+
+import cc.mrbird.febs.common.entity.auth.OauthCliendetails;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ *  Mapper
+ *
+ * @author MrBird
+ * @date 2019-09-09 14:13:23
+ */
+public interface OauthCliendetailsMapper extends BaseMapper<OauthCliendetails> {
+
+}

+ 4 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsAuthProperties.java

@@ -30,4 +30,8 @@ public class FebsAuthProperties {
      * JWT加签密钥
      */
     private String jwtAccessKey;
+    /**
+     * 是否启用jwt
+     */
+    private Boolean enableJwt;
 }

+ 55 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/IOauthCliendetailsService.java

@@ -0,0 +1,55 @@
+package cc.mrbird.febs.auth.service;
+
+
+import cc.mrbird.febs.common.entity.QueryRequest;
+import cc.mrbird.febs.common.entity.auth.OauthCliendetails;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ *  Service接口
+ *
+ * @author Yuuki
+ * @date 2019年9月23日17:14:38
+ */
+public interface IOauthCliendetailsService extends IService<OauthCliendetails> {
+    /**
+     * 查询(分页)
+     *
+     * @param request QueryRequest
+     * @param oauthCliendetails oauthCliendetails
+     * @return IPage<OauthCliendetails>
+     */
+    IPage<OauthCliendetails> findOauthCliendetailss(QueryRequest request, OauthCliendetails oauthCliendetails);
+
+    /**
+     * 查询(所有)
+     *
+     * @param oauthCliendetails oauthCliendetails
+     * @return List<OauthCliendetails>
+     */
+    List<OauthCliendetails> findOauthCliendetailss(OauthCliendetails oauthCliendetails);
+
+    /**
+     * 新增
+     *
+     * @param oauthCliendetails oauthCliendetails
+     */
+    void createOauthCliendetails(OauthCliendetails oauthCliendetails);
+
+    /**
+     * 修改
+     *
+     * @param oauthCliendetails oauthCliendetails
+     */
+    void updateOauthCliendetails(OauthCliendetails oauthCliendetails);
+
+    /**
+     * 删除
+     *
+     * @param oauthCliendetails oauthCliendetails
+     */
+    void deleteOauthCliendetails(OauthCliendetails oauthCliendetails);
+}

+ 99 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/RedisClientDetailsService.java

@@ -0,0 +1,99 @@
+package cc.mrbird.febs.auth.service;
+
+import cc.mrbird.febs.common.service.RedisService;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.security.oauth2.provider.client.BaseClientDetails;
+import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
+import org.springframework.stereotype.Service;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+/**
+ * 自定义oauth客户端适配
+ * @author Yuuki
+ * @date 2019年9月23日17:23:04
+ */
+@Service
+@Slf4j
+public class RedisClientDetailsService extends JdbcClientDetailsService {
+
+    @Autowired
+    RedisService redisService;
+
+    public RedisClientDetailsService(DataSource dataSource) {
+        super(dataSource);
+    }
+
+
+    /**
+     * 缓存client的redis key,这里是hash结构存储
+     */
+    private static final String CACHE_CLIENT_KEY = "client_details";
+
+    @Override
+    public ClientDetails loadClientByClientId(String clientId) throws InvalidClientException {
+        ClientDetails clientDetails = null;
+
+        // 先从redis获取
+        String value = (String) redisService.hget(CACHE_CLIENT_KEY,clientId);
+        if (Strings.isBlank(value)) {
+            clientDetails = cacheAndGetClient(clientId);
+        } else {
+            clientDetails = JSONObject.parseObject(value, BaseClientDetails.class);
+        }
+
+        return clientDetails;
+    }
+
+    /**
+     * 缓存client并返回client
+     *
+     * @param clientId
+     */
+    public ClientDetails cacheAndGetClient(String clientId) {
+        // 从数据库读取
+        ClientDetails clientDetails = super.loadClientByClientId(clientId);
+        if (clientDetails != null) {// 写入redis缓存
+            redisService.hset(CACHE_CLIENT_KEY,clientId,JSONObject.toJSONString(clientDetails));
+            log.info("缓存clientId:{},{}", clientId, clientDetails);
+        }
+
+        return clientDetails;
+    }
+
+    /**
+     * 删除redis缓存
+     *
+     * @param clientId
+     */
+    public void removeRedisCache(String clientId) {
+        redisService.hdel(CACHE_CLIENT_KEY,clientId);
+    }
+
+    /**
+     * 将oauth_client_details全表刷入redis
+     */
+    public void loadAllClientToCache() {
+        if (redisService.hasKey(CACHE_CLIENT_KEY)) {
+            return;
+        }
+        log.info("将oauth_client_details全表刷入redis");
+
+        List<ClientDetails> list = super.listClientDetails();
+        if (list.isEmpty()) {
+            log.error("oauth_client_details表数据为空,请检查");
+            return;
+        }
+
+        list.forEach(client -> {
+            redisService.hset(CACHE_CLIENT_KEY,client.getClientId(),JSONObject.toJSONString(client));
+        });
+    }
+
+}

+ 73 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/impl/OauthCliendetailsServiceImpl.java

@@ -0,0 +1,73 @@
+package cc.mrbird.febs.auth.service.impl;
+
+
+import cc.mrbird.febs.auth.mapper.OauthCliendetailsMapper;
+import cc.mrbird.febs.auth.service.IOauthCliendetailsService;
+import cc.mrbird.febs.auth.service.RedisClientDetailsService;
+import cc.mrbird.febs.common.entity.QueryRequest;
+import cc.mrbird.febs.common.entity.auth.OauthCliendetails;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ *  Service实现
+ *
+ * @author Yuuki
+ * @date 2019年9月23日17:22:03
+ */
+@Service
+@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
+public class OauthCliendetailsServiceImpl extends ServiceImpl<OauthCliendetailsMapper, OauthCliendetails> implements IOauthCliendetailsService {
+
+    @Autowired
+    private OauthCliendetailsMapper oauthCliendetailsMapper;
+    @Autowired
+    private RedisClientDetailsService redisClientDetailsService;
+
+    @Override
+    public IPage<OauthCliendetails> findOauthCliendetailss(QueryRequest request, OauthCliendetails oauthCliendetails) {
+        LambdaQueryWrapper<OauthCliendetails> queryWrapper = new LambdaQueryWrapper<>();
+        // TODO 设置查询条件
+        Page<OauthCliendetails> page = new Page<>(request.getPageNum(), request.getPageSize());
+        return this.page(page, queryWrapper);
+    }
+
+    @Override
+    public List<OauthCliendetails> findOauthCliendetailss(OauthCliendetails oauthCliendetails) {
+	    LambdaQueryWrapper<OauthCliendetails> queryWrapper = new LambdaQueryWrapper<>();
+		// TODO 设置查询条件
+		return this.baseMapper.selectList(queryWrapper);
+    }
+
+    @Override
+    @Transactional
+    public void createOauthCliendetails(OauthCliendetails oauthCliendetails) {
+        this.save(oauthCliendetails);
+    }
+
+    @Override
+    @Transactional
+    public void updateOauthCliendetails(OauthCliendetails oauthCliendetails) {
+        this.saveOrUpdate(oauthCliendetails);
+        // 更新客户端缓存
+        redisClientDetailsService.cacheAndGetClient(oauthCliendetails.getClientId());
+    }
+
+    @Override
+    @Transactional
+    public void deleteOauthCliendetails(OauthCliendetails oauthCliendetails) {
+        LambdaQueryWrapper<OauthCliendetails> wapper = new LambdaQueryWrapper<>();
+	    // TODO 设置删除条件
+	    this.remove(wapper);
+	    // 移除客户端缓存
+	    redisClientDetailsService.removeRedisCache(oauthCliendetails.getClientId());
+	}
+}

+ 1 - 0
febs-auth/src/main/resources/febs-auth.properties

@@ -12,6 +12,7 @@ febs.auth.clients[1].scope=test
 febs.auth.clients[1].accessTokenValiditySeconds=3600
 
 febs.auth.anonUrl=/actuator/**,/captcha
+febs.auth.enableJwt=false
 febs.auth.jwtAccessKey=febs
 
 febs.auth.code.time=120

+ 18 - 0
febs-auth/src/main/resources/mapper/OauthCliendetailsMapper.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cc.mrbird.febs.auth.mapper.OauthCliendetailsMapper">
+
+    <resultMap id="oauthCliendetails" type="cc.mrbird.febs.common.entity.auth.OauthCliendetails">
+        <id column="client_id" jdbcType="varchar" property="clientId"/>
+        <result column="resource_ids" jdbcType="varchar" property="resourceIds"/>
+        <result column="client_secret" jdbcType="varchar" property="clientSecret"/>
+        <result column="scope" jdbcType="varchar" property="scope"/>
+        <result column="authorized_grant_types" jdbcType="varchar" property="authorizedGrantTypes"/>
+        <result column="web_server_redirect_uri" jdbcType="varchar" property="webServerRedirectUri"/>
+        <result column="authorities" jdbcType="varchar" property="authorities"/>
+        <result column="access_token_validity" jdbcType="int" property="accessTokenValidity"/>
+        <result column="refresh_token_validity" jdbcType="int" property="refreshTokenValidity"/>
+        <result column="additional_information" jdbcType="varchar" property="additionalInformation"/>
+        <result column="autoapprove" jdbcType="tinyint" property="autoapprove"/>
+    </resultMap>
+</mapper>

+ 86 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/auth/OauthCliendetails.java

@@ -0,0 +1,86 @@
+package cc.mrbird.febs.common.entity.auth;
+
+
+import lombok.Data;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+/**
+ *  Entity
+ *
+ * @author MrBird
+ * @date 2019-09-09 14:13:23
+ */
+@Data
+@TableName("oauth_client_details")
+public class OauthCliendetails {
+
+    /**
+     * 
+     */
+    @TableId(value = "client_id", type = IdType.AUTO)
+    private String clientId;
+
+    /**
+     * 
+     */
+    @TableField("resource_ids")
+    private String resourceIds;
+
+    /**
+     * 
+     */
+    @TableField("client_secret")
+    private String clientSecret;
+
+    /**
+     * 
+     */
+    @TableField("scope")
+    private String scope;
+
+    /**
+     * 
+     */
+    @TableField("authorized_grant_types")
+    private String authorizedGrantTypes;
+
+    /**
+     * 
+     */
+    @TableField("web_server_redirect_uri")
+    private String webServerRedirectUri;
+
+    /**
+     * 
+     */
+    @TableField("authorities")
+    private String authorities;
+
+    /**
+     * 
+     */
+    @TableField("access_token_validity")
+    private Integer accessTokenValidity;
+
+    /**
+     * 
+     */
+    @TableField("refresh_token_validity")
+    private Integer refreshTokenValidity;
+
+    /**
+     * 
+     */
+    @TableField("additional_information")
+    private String additionalInformation;
+
+    /**
+     * 
+     */
+    @TableField("autoapprove")
+    private Byte autoapprove;
+
+}