فهرست منبع

:zap: 增加多终端令牌认证

smallchill 7 سال پیش
والد
کامیت
66d553a08a

+ 2 - 2
pom.xml

@@ -7,10 +7,10 @@
     <groupId>org.springblade</groupId>
     <artifactId>BladeX-Boot</artifactId>
     <packaging>jar</packaging>
-    <version>2.0.0.RC7</version>
+    <version>2.0.0.RC8</version>
 
     <properties>
-        <bladex.tool.version>2.0.0.RC7</bladex.tool.version>
+        <bladex.tool.version>2.0.0.RC8</bladex.tool.version>
 
         <java.version>1.8</java.version>
         <swagger.version>2.9.2</swagger.version>

+ 0 - 8
src/main/java/org/springblade/common/config/BladeConfiguration.java

@@ -20,7 +20,6 @@ package org.springblade.common.config;
 import org.springblade.core.secure.registry.SecureRegistry;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 /**
@@ -44,11 +43,4 @@ public class BladeConfiguration implements WebMvcConfigurer {
 		return secureRegistry;
 	}
 
-	@Override
-	public void addResourceHandlers(ResourceHandlerRegistry registry) {
-		registry.addResourceHandler("/js/**").addResourceLocations("classpath:/js/");
-		registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
-		registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
-	}
-
 }

+ 103 - 5
src/main/java/org/springblade/core/secure/utils/SecureUtil.java

@@ -20,12 +20,14 @@ import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.JwtBuilder;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.SneakyThrows;
 import org.springblade.core.launch.constant.TokenConstant;
 import org.springblade.core.secure.BladeUser;
-import org.springblade.core.tool.utils.Charsets;
-import org.springblade.core.tool.utils.Func;
-import org.springblade.core.tool.utils.StringPool;
-import org.springblade.core.tool.utils.WebUtil;
+import org.springblade.core.secure.constant.SecureConstant;
+import org.springblade.core.secure.exception.SecureException;
+import org.springblade.core.secure.provider.IClientDetails;
+import org.springblade.core.secure.provider.IClientDetailsService;
+import org.springblade.core.tool.utils.*;
 
 import javax.crypto.spec.SecretKeySpec;
 import javax.servlet.http.HttpServletRequest;
@@ -48,9 +50,16 @@ public class SecureUtil {
 	private final static String USER_NAME = TokenConstant.USER_NAME;
 	private final static String ROLE_NAME = TokenConstant.ROLE_NAME;
 	private final static String TENANT_CODE = TokenConstant.TENANT_CODE;
+	private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
 	private final static Integer AUTH_LENGTH = TokenConstant.AUTH_LENGTH;
 	private static String BASE64_SECURITY = Base64.getEncoder().encodeToString(TokenConstant.SIGN_KEY.getBytes(Charsets.UTF_8));
 
+	private static IClientDetailsService clientDetailsService;
+
+	static {
+		clientDetailsService = SpringUtil.getBean(IClientDetailsService.class);
+	}
+
 	/**
 	 * 获取用户信息
 	 *
@@ -84,6 +93,7 @@ public class SecureUtil {
 		if (claims == null) {
 			return null;
 		}
+		String clientId = Func.toStr(claims.get(SecureUtil.CLIENT_ID));
 		Integer userId = Func.toInt(claims.get(SecureUtil.USER_ID));
 		String tenantCode = Func.toStr(claims.get(SecureUtil.TENANT_CODE));
 		String roleId = Func.toStr(claims.get(SecureUtil.ROLE_ID));
@@ -91,6 +101,7 @@ public class SecureUtil {
 		String roleName = Func.toStr(claims.get(SecureUtil.ROLE_NAME));
 		String userName = Func.toStr(claims.get(SecureUtil.USER_NAME));
 		BladeUser bladeUser = new BladeUser();
+		bladeUser.setClientId(clientId);
 		bladeUser.setUserId(userId);
 		bladeUser.setTenantCode(tenantCode);
 		bladeUser.setAccount(account);
@@ -206,6 +217,27 @@ public class SecureUtil {
 		return (null == user) ? StringPool.EMPTY : user.getTenantCode();
 	}
 
+	/**
+	 * 获取客户端id
+	 *
+	 * @return tenantCode
+	 */
+	public static String getClientId() {
+		BladeUser user = getUser();
+		return (null == user) ? StringPool.EMPTY : user.getClientId();
+	}
+
+	/**
+	 * 获取客户端id
+	 *
+	 * @param request request
+	 * @return tenantCode
+	 */
+	public static String getClientId(HttpServletRequest request) {
+		BladeUser user = getUser(request);
+		return (null == user) ? StringPool.EMPTY : user.getClientId();
+	}
+
 	/**
 	 * 获取Claims
 	 *
@@ -260,7 +292,7 @@ public class SecureUtil {
 	}
 
 	/**
-	 * 创建jwt
+	 * 创建令牌
 	 *
 	 * @param user     user
 	 * @param audience audience
@@ -269,6 +301,17 @@ public class SecureUtil {
 	 * @return jwt
 	 */
 	public static String createJWT(Map<String, String> user, String audience, String issuer, boolean isExpire) {
+
+		String[] tokens = extractAndDecodeHeader();
+		assert tokens.length == 2;
+		String clientId = tokens[0];
+		String clientSecret = tokens[1];
+
+		// 校验客户端信息
+		if (!validateClient(clientId, clientSecret)) {
+			throw new SecureException("客户端认证失败!");
+		}
+
 		SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
 
 		long nowMillis = System.currentTimeMillis();
@@ -287,6 +330,9 @@ public class SecureUtil {
 		//设置JWT参数
 		user.forEach(builder::claim);
 
+		//设置应用id
+		builder.claim(CLIENT_ID, clientId);
+
 		//添加Token过期时间
 		if (isExpire) {
 			long expMillis = nowMillis + getExpire();
@@ -322,4 +368,56 @@ public class SecureUtil {
 		return (int) (getExpire() / 1000);
 	}
 
+	/**
+	 * 客户端信息解码
+	 */
+	@SneakyThrows
+	public static String[] extractAndDecodeHeader() {
+		// 获取请求头客户端信息
+		String header = Objects.requireNonNull(WebUtil.getRequest()).getHeader(SecureConstant.BASIC_HEADER_KEY);
+		if (header == null || !header.startsWith(SecureConstant.BASIC_HEADER_PREFIX)) {
+			throw new SecureException("No client information in request header");
+		}
+		byte[] base64Token = header.substring(6).getBytes(Charsets.UTF_8_NAME);
+
+		byte[] decoded;
+		try {
+			decoded = Base64.getDecoder().decode(base64Token);
+		} catch (IllegalArgumentException var7) {
+			throw new RuntimeException("Failed to decode basic authentication token");
+		}
+
+		String token = new String(decoded, Charsets.UTF_8_NAME);
+		int index = token.indexOf(StringPool.COLON);
+		if (index == -1) {
+			throw new RuntimeException("Invalid basic authentication token");
+		} else {
+			return new String[]{token.substring(0, index), token.substring(index + 1)};
+		}
+	}
+
+	/**
+	 * 获取请求头中的客户端id
+	 */
+	public static String getClientIdFromHeader() {
+		String[] tokens = extractAndDecodeHeader();
+		assert tokens.length == 2;
+		return tokens[0];
+	}
+
+	/**
+	 * 校验Client
+	 *
+	 * @param clientId     客户端id
+	 * @param clientSecret 客户端密钥
+	 * @return boolean
+	 */
+	private static boolean validateClient(String clientId, String clientSecret) {
+		IClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
+		if (clientDetails != null) {
+			return StringUtil.equals(clientId, clientDetails.getClientId()) && StringUtil.equals(clientSecret, clientDetails.getClientSecret());
+		}
+		return false;
+	}
+
 }

+ 15 - 13
src/main/java/org/springblade/modules/auth/AuthController.java

@@ -85,19 +85,21 @@ public class AuthController {
 		param.put(TokenConstant.ROLE_NAME, Func.join(userInfo.getRoles()));
 
 		//拼装accessToken
-		String accessToken = SecureUtil.createJWT(param, "audience", "issuser", true);
-
-		//返回accessToken
-		authInfo.set(TokenConstant.ACCOUNT, user.getAccount())
-			.set(TokenConstant.USER_NAME, user.getRealName())
-			.set(TokenConstant.ROLE_NAME, Func.join(userInfo.getRoles()))
-			.set(TokenConstant.AVATAR, TokenConstant.DEFAULT_AVATAR)
-			.set(TokenConstant.ACCESS_TOKEN, accessToken)
-			.set(TokenConstant.REFRESH_TOKEN, accessToken)
-			.set(TokenConstant.TOKEN_TYPE, TokenConstant.BEARER)
-			.set(TokenConstant.EXPIRES_IN, SecureUtil.getExpireSeconds())
-			.set(TokenConstant.LICENSE, TokenConstant.LICENSE_NAME);
-		return authInfo;
+		try {
+			String accessToken = SecureUtil.createJWT(param, "audience", "issuser", true);
+			//返回accessToken
+			return authInfo.set(TokenConstant.ACCOUNT, user.getAccount())
+				.set(TokenConstant.USER_NAME, user.getRealName())
+				.set(TokenConstant.ROLE_NAME, Func.join(userInfo.getRoles()))
+				.set(TokenConstant.AVATAR, TokenConstant.DEFAULT_AVATAR)
+				.set(TokenConstant.ACCESS_TOKEN, accessToken)
+				.set(TokenConstant.REFRESH_TOKEN, accessToken)
+				.set(TokenConstant.TOKEN_TYPE, TokenConstant.BEARER)
+				.set(TokenConstant.EXPIRES_IN, SecureUtil.getExpireSeconds())
+				.set(TokenConstant.LICENSE, TokenConstant.LICENSE_NAME);
+		} catch (Exception ex) {
+			return authInfo.set("error_code", HttpServletResponse.SC_UNAUTHORIZED).set("error_description", ex.getMessage());
+		}
 	}
 
 }

+ 108 - 0
src/main/java/org/springblade/modules/system/controller/AuthClientController.java

@@ -0,0 +1,108 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.system.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.modules.system.entity.AuthClient;
+import org.springblade.modules.system.service.IAuthClientService;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+import javax.validation.Valid;
+
+/**
+ *  应用管理控制器
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/blade-system/client")
+@ApiIgnore
+@Api(value = "应用管理", tags = "接口")
+public class AuthClientController extends BladeController {
+
+	private IAuthClientService clientService;
+
+	/**
+	* 详情
+	*/
+	@GetMapping("/detail")
+	@ApiOperation(value = "详情", notes = "传入client", position = 1)
+	public R<AuthClient> detail(AuthClient authClient) {
+		AuthClient detail = clientService.getOne(Condition.getQueryWrapper(authClient));
+		return R.data(detail);
+	}
+
+	/**
+	* 分页 
+	*/
+	@GetMapping("/list")
+	@ApiOperation(value = "分页", notes = "传入client", position = 2)
+	public R<IPage<AuthClient>> list(AuthClient authClient, Query query) {
+		IPage<AuthClient> pages = clientService.page(Condition.getPage(query), Condition.getQueryWrapper(authClient));
+		return R.data(pages);
+	}
+
+	/**
+	* 新增 
+	*/
+	@PostMapping("/save")
+	@ApiOperation(value = "新增", notes = "传入client", position = 4)
+	public R save(@Valid @RequestBody AuthClient authClient) {
+		return R.status(clientService.save(authClient));
+	}
+
+	/**
+	* 修改 
+	*/
+	@PostMapping("/update")
+	@ApiOperation(value = "修改", notes = "传入client", position = 5)
+	public R update(@Valid @RequestBody AuthClient authClient) {
+		return R.status(clientService.updateById(authClient));
+	}
+
+	/**
+	* 新增或修改 
+	*/
+	@PostMapping("/submit")
+	@ApiOperation(value = "新增或修改", notes = "传入client", position = 6)
+	public R submit(@Valid @RequestBody AuthClient authClient) {
+		return R.status(clientService.saveOrUpdate(authClient));
+	}
+
+	
+	/**
+	* 删除 
+	*/
+	@PostMapping("/remove")
+	@ApiOperation(value = "逻辑删除", notes = "传入ids", position = 7)
+	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+		return R.status(clientService.deleteLogic(Func.toIntList(ids)));
+	}
+
+	
+}

+ 97 - 0
src/main/java/org/springblade/modules/system/entity/AuthClient.java

@@ -0,0 +1,97 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.system.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * 实体类
+ *
+ * @author BladeX
+ * @since 2019-03-24
+ */
+@Data
+@TableName("blade_client")
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "Client对象", description = "Client对象")
+public class AuthClient extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 客户端id
+     */
+    @ApiModelProperty(value = "客户端id")
+    private String clientId;
+    /**
+     * 客户端密钥
+     */
+    @ApiModelProperty(value = "客户端密钥")
+    private String clientSecret;
+    /**
+     * 资源集合
+     */
+    @ApiModelProperty(value = "资源集合")
+    private String resourceIds;
+    /**
+     * 授权范围
+     */
+    @ApiModelProperty(value = "授权范围")
+    private String scope;
+    /**
+     * 授权类型
+     */
+    @ApiModelProperty(value = "授权类型")
+    private String authorizedGrantTypes;
+    /**
+     * 回调地址
+     */
+    @ApiModelProperty(value = "回调地址")
+    private String webServerRedirectUri;
+    /**
+     * 权限
+     */
+    @ApiModelProperty(value = "权限")
+    private String authorities;
+    /**
+     * 令牌过期秒数
+     */
+    @ApiModelProperty(value = "令牌过期秒数")
+    private Integer accessTokenValidity;
+    /**
+     * 刷新令牌过期秒数
+     */
+    @ApiModelProperty(value = "刷新令牌过期秒数")
+    private Integer refreshTokenValidity;
+    /**
+     * 附加说明
+     */
+    @ApiModelProperty(value = "附加说明")
+    private String additionalInformation;
+    /**
+     * 自动授权
+     */
+    @ApiModelProperty(value = "自动授权")
+    private String autoapprove;
+
+
+}

+ 29 - 0
src/main/java/org/springblade/modules/system/mapper/AuthClientMapper.java

@@ -0,0 +1,29 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.modules.system.entity.AuthClient;
+
+/**
+ * Mapper 接口
+ *
+ * @author Chill
+ */
+public interface AuthClientMapper extends BaseMapper<AuthClient> {
+
+}

+ 27 - 0
src/main/java/org/springblade/modules/system/mapper/AuthClientMapper.xml

@@ -0,0 +1,27 @@
+<?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="org.springblade.modules.system.mapper.AuthClientMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="clientResultMap" type="org.springblade.modules.system.entity.AuthClient">
+        <result column="id" property="id"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="status" property="status"/>
+        <result column="is_deleted" property="isDeleted"/>
+        <result column="client_id" property="clientId"/>
+        <result column="client_secret" property="clientSecret"/>
+        <result column="resources_ids" property="resourceIds"/>
+        <result column="scope" property="scope"/>
+        <result column="authorized_grant_types" property="authorizedGrantTypes"/>
+        <result column="web_server_redirect_uri" property="webServerRedirectUri"/>
+        <result column="authorities" property="authorities"/>
+        <result column="access_token_validity" property="accessTokenValidity"/>
+        <result column="refresh_token_validity" property="refreshTokenValidity"/>
+        <result column="additional_information" property="additionalInformation"/>
+        <result column="autoapprove" property="autoapprove"/>
+    </resultMap>
+
+</mapper>

+ 29 - 0
src/main/java/org/springblade/modules/system/service/IAuthClientService.java

@@ -0,0 +1,29 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.system.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.modules.system.entity.AuthClient;
+
+/**
+ *  服务类
+ *
+ * @author Chill
+ */
+public interface IAuthClientService extends BaseService<AuthClient> {
+
+}

+ 33 - 0
src/main/java/org/springblade/modules/system/service/impl/AuthClientServiceImpl.java

@@ -0,0 +1,33 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.modules.system.service.impl;
+
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.modules.system.entity.AuthClient;
+import org.springblade.modules.system.mapper.AuthClientMapper;
+import org.springblade.modules.system.service.IAuthClientService;
+import org.springframework.stereotype.Service;
+
+/**
+ *  服务实现类
+ *
+ * @author Chill
+ */
+@Service
+public class AuthClientServiceImpl extends BaseServiceImpl<AuthClientMapper, AuthClient> implements IAuthClientService {
+
+}

+ 12 - 1
src/main/resources/application.yml

@@ -83,8 +83,19 @@ swagger:
     email: smallchill@163.com
     url: https://gitee.com/smallc
 
-# 租户表维护
+#blade配置
 blade:
+  secure:
+    url:
+      exclude-patterns:
+        - /blade-test/**
+    client:
+      - client-id: sword
+        path-patterns:
+          - /blade-sword/**
+      - client-id: saber
+        path-patterns:
+          - /blade-saber/**
   tenant:
     column: tenant_code
     tables: