@@ -0,0 +1,18 @@ | |||||
FROM pig4cloud/java:8-jre | |||||
MAINTAINER wangiegie@gmail.com | |||||
ENV TZ=Asia/Shanghai | |||||
ENV JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" | |||||
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone | |||||
RUN mkdir -p /pigx-auth | |||||
WORKDIR /pigx-auth | |||||
EXPOSE 3000 | |||||
ADD ./target/pigx-auth.jar ./ | |||||
CMD sleep 120;java $JAVA_OPTS -jar pigx-auth.jar |
@@ -0,0 +1,129 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!-- | |||||
~ | |||||
~ Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
~ | |||||
--> | |||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<parent> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx</artifactId> | |||||
<version>3.9.0</version> | |||||
</parent> | |||||
<artifactId>pigx-auth</artifactId> | |||||
<packaging>jar</packaging> | |||||
<description>pigx 认证授权中心,基于 spring security oAuth2</description> | |||||
<dependencies> | |||||
<!--注册中心客户端--> | |||||
<dependency> | |||||
<groupId>com.alibaba.cloud</groupId> | |||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> | |||||
</dependency> | |||||
<!--配置中心客户端--> | |||||
<dependency> | |||||
<groupId>com.alibaba.cloud</groupId> | |||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> | |||||
</dependency> | |||||
<!--upms api、model 模块--> | |||||
<dependency> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx-upms-api</artifactId> | |||||
</dependency> | |||||
<!--log--> | |||||
<dependency> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx-common-log</artifactId> | |||||
</dependency> | |||||
<!--security--> | |||||
<dependency> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx-common-security</artifactId> | |||||
</dependency> | |||||
<!--feign 依赖--> | |||||
<dependency> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx-common-feign</artifactId> | |||||
</dependency> | |||||
<!--mysql 驱动--> | |||||
<dependency> | |||||
<groupId>mysql</groupId> | |||||
<artifactId>mysql-connector-java</artifactId> | |||||
</dependency> | |||||
<!--缓存操作--> | |||||
<dependency> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx-common-data</artifactId> | |||||
</dependency> | |||||
<!--sentinel 依赖--> | |||||
<dependency> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx-common-sentinel</artifactId> | |||||
</dependency> | |||||
<!--路由控制--> | |||||
<dependency> | |||||
<groupId>com.pig4cloud</groupId> | |||||
<artifactId>pigx-common-gray</artifactId> | |||||
</dependency> | |||||
<!--JDBC相关--> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-jdbc</artifactId> | |||||
</dependency> | |||||
<!-- druid 连接池 --> | |||||
<dependency> | |||||
<groupId>com.alibaba</groupId> | |||||
<artifactId>druid-spring-boot-starter</artifactId> | |||||
</dependency> | |||||
<!--freemarker--> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-freemarker</artifactId> | |||||
</dependency> | |||||
<!--web 模块--> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-web</artifactId> | |||||
</dependency> | |||||
<!--undertow容器--> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-undertow</artifactId> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-maven-plugin</artifactId> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>io.fabric8</groupId> | |||||
<artifactId>docker-maven-plugin</artifactId> | |||||
<configuration> | |||||
<skip>false</skip> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> |
@@ -0,0 +1,38 @@ | |||||
/* | |||||
* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
* | |||||
*/ | |||||
package com.pig4cloud.pigx.auth; | |||||
import com.pig4cloud.pigx.common.feign.annotation.EnablePigxFeignClients; | |||||
import org.springframework.boot.SpringApplication; | |||||
import org.springframework.cloud.client.SpringCloudApplication; | |||||
/** | |||||
* @author lengleng | |||||
* @date 2020-02-08 认证授权中心 | |||||
*/ | |||||
@SpringCloudApplication | |||||
@EnablePigxFeignClients | |||||
public class PigxAuthApplication { | |||||
public static void main(String[] args) { | |||||
SpringApplication.run(PigxAuthApplication.class, args); | |||||
} | |||||
} |
@@ -0,0 +1,98 @@ | |||||
/* | |||||
* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
* | |||||
*/ | |||||
package com.pig4cloud.pigx.auth.config; | |||||
import cn.hutool.core.util.StrUtil; | |||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants; | |||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; | |||||
import com.pig4cloud.pigx.common.security.component.PigxWebResponseExceptionTranslator; | |||||
import lombok.AllArgsConstructor; | |||||
import lombok.SneakyThrows; | |||||
import org.springframework.context.annotation.Bean; | |||||
import org.springframework.context.annotation.Configuration; | |||||
import org.springframework.data.redis.connection.RedisConnectionFactory; | |||||
import org.springframework.http.HttpMethod; | |||||
import org.springframework.security.authentication.AuthenticationManager; | |||||
import org.springframework.security.core.userdetails.UserDetailsService; | |||||
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.ClientDetailsService; | |||||
import org.springframework.security.oauth2.provider.OAuth2Authentication; | |||||
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; | |||||
import org.springframework.security.oauth2.provider.token.TokenEnhancer; | |||||
import org.springframework.security.oauth2.provider.token.TokenStore; | |||||
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; | |||||
/** | |||||
* @author lengleng | |||||
* @date 2018/6/22 认证服务器配置 | |||||
*/ | |||||
@Configuration | |||||
@AllArgsConstructor | |||||
@EnableAuthorizationServer | |||||
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { | |||||
private final ClientDetailsService pigxClientDetailsServiceImpl; | |||||
private final AuthenticationManager authenticationManagerBean; | |||||
private final RedisConnectionFactory redisConnectionFactory; | |||||
private final UserDetailsService pigxUserDetailsService; | |||||
private final TokenEnhancer pigxTokenEnhancer; | |||||
@Override | |||||
@SneakyThrows | |||||
public void configure(ClientDetailsServiceConfigurer clients) { | |||||
clients.withClientDetails(pigxClientDetailsServiceImpl); | |||||
} | |||||
@Override | |||||
public void configure(AuthorizationServerSecurityConfigurer oauthServer) { | |||||
oauthServer.allowFormAuthenticationForClients().checkTokenAccess("isAuthenticated()"); | |||||
} | |||||
@Override | |||||
public void configure(AuthorizationServerEndpointsConfigurer endpoints) { | |||||
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenStore(tokenStore()) | |||||
.tokenEnhancer(pigxTokenEnhancer).userDetailsService(pigxUserDetailsService) | |||||
.authenticationManager(authenticationManagerBean).reuseRefreshTokens(false) | |||||
.pathMapping("/oauth/confirm_access", "/token/confirm_access") | |||||
.exceptionTranslator(new PigxWebResponseExceptionTranslator()); | |||||
} | |||||
@Bean | |||||
public TokenStore tokenStore() { | |||||
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory); | |||||
tokenStore.setPrefix(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX); | |||||
tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() { | |||||
@Override | |||||
public String extractKey(OAuth2Authentication authentication) { | |||||
return super.extractKey(authentication) + StrUtil.COLON + TenantContextHolder.getTenantId(); | |||||
} | |||||
}); | |||||
return tokenStore; | |||||
} | |||||
} |
@@ -0,0 +1,103 @@ | |||||
/* | |||||
* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
* | |||||
*/ | |||||
package com.pig4cloud.pigx.auth.config; | |||||
import com.pig4cloud.pigx.common.security.handler.FormAuthenticationFailureHandler; | |||||
import com.pig4cloud.pigx.common.security.handler.MobileLoginSuccessHandler; | |||||
import com.pig4cloud.pigx.common.security.mobile.MobileSecurityConfigurer; | |||||
import lombok.SneakyThrows; | |||||
import org.springframework.context.annotation.Bean; | |||||
import org.springframework.context.annotation.Configuration; | |||||
import org.springframework.context.annotation.Primary; | |||||
import org.springframework.core.annotation.Order; | |||||
import org.springframework.http.HttpHeaders; | |||||
import org.springframework.security.authentication.AuthenticationManager; | |||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | |||||
import org.springframework.security.config.annotation.web.builders.WebSecurity; | |||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | |||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories; | |||||
import org.springframework.security.crypto.password.PasswordEncoder; | |||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler; | |||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; | |||||
/** | |||||
* @author lengleng | |||||
* @date 2018/6/22 认证相关配置 | |||||
*/ | |||||
@Primary | |||||
@Order(90) | |||||
@Configuration | |||||
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { | |||||
@Override | |||||
@SneakyThrows | |||||
protected void configure(HttpSecurity http) { | |||||
http.formLogin().loginPage("/token/login").loginProcessingUrl("/token/form") | |||||
.failureHandler(authenticationFailureHandler()).and().logout() | |||||
.logoutSuccessHandler((request, response, authentication) -> { | |||||
String referer = request.getHeader(HttpHeaders.REFERER); | |||||
response.sendRedirect(referer); | |||||
}).deleteCookies("JSESSIONID").invalidateHttpSession(true).and().authorizeRequests() | |||||
.antMatchers("/token/**", "/actuator/**", "/mobile/**").permitAll().anyRequest().authenticated().and() | |||||
.csrf().disable().apply(mobileSecurityConfigurer()); | |||||
} | |||||
/** | |||||
* 不拦截静态资源 | |||||
* @param web | |||||
*/ | |||||
@Override | |||||
public void configure(WebSecurity web) { | |||||
web.ignoring().antMatchers("/css/**"); | |||||
} | |||||
@Bean | |||||
@Override | |||||
@SneakyThrows | |||||
public AuthenticationManager authenticationManagerBean() { | |||||
return super.authenticationManagerBean(); | |||||
} | |||||
@Bean | |||||
public AuthenticationFailureHandler authenticationFailureHandler() { | |||||
return new FormAuthenticationFailureHandler(); | |||||
} | |||||
@Bean | |||||
public AuthenticationSuccessHandler mobileLoginSuccessHandler() { | |||||
return new MobileLoginSuccessHandler(); | |||||
} | |||||
@Bean | |||||
public MobileSecurityConfigurer mobileSecurityConfigurer() { | |||||
return new MobileSecurityConfigurer(); | |||||
} | |||||
/** | |||||
* https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated | |||||
* Encoded password does not look like BCrypt | |||||
* @return PasswordEncoder | |||||
*/ | |||||
@Bean | |||||
public PasswordEncoder passwordEncoder() { | |||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder(); | |||||
} | |||||
} |
@@ -0,0 +1,213 @@ | |||||
/* | |||||
* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
* | |||||
*/ | |||||
package com.pig4cloud.pigx.auth.endpoint; | |||||
import cn.hutool.core.map.MapUtil; | |||||
import cn.hutool.core.util.StrUtil; | |||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | |||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants; | |||||
import com.pig4cloud.pigx.common.core.constant.PaginationConstants; | |||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants; | |||||
import com.pig4cloud.pigx.common.core.util.R; | |||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; | |||||
import com.pig4cloud.pigx.common.security.annotation.Inner; | |||||
import com.pig4cloud.pigx.common.security.util.SecurityUtils; | |||||
import lombok.AllArgsConstructor; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.cache.CacheManager; | |||||
import org.springframework.data.redis.core.ConvertingCursor; | |||||
import org.springframework.data.redis.core.Cursor; | |||||
import org.springframework.data.redis.core.RedisTemplate; | |||||
import org.springframework.data.redis.core.ScanOptions; | |||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; | |||||
import org.springframework.data.redis.serializer.RedisSerializer; | |||||
import org.springframework.data.redis.serializer.StringRedisSerializer; | |||||
import org.springframework.http.HttpHeaders; | |||||
import org.springframework.security.oauth2.common.OAuth2AccessToken; | |||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken; | |||||
import org.springframework.security.oauth2.provider.AuthorizationRequest; | |||||
import org.springframework.security.oauth2.provider.ClientDetails; | |||||
import org.springframework.security.oauth2.provider.ClientDetailsService; | |||||
import org.springframework.security.oauth2.provider.OAuth2Authentication; | |||||
import org.springframework.security.oauth2.provider.token.TokenStore; | |||||
import org.springframework.web.bind.annotation.*; | |||||
import org.springframework.web.servlet.ModelAndView; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpSession; | |||||
import java.io.IOException; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
/** | |||||
* @author lengleng | |||||
* @date 2018/6/24 删除token端点 | |||||
*/ | |||||
@Slf4j | |||||
@RestController | |||||
@AllArgsConstructor | |||||
@RequestMapping("/token") | |||||
public class PigxTokenEndpoint { | |||||
private static final String PIGX_OAUTH_ACCESS = SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX | |||||
+ "auth_to_access:"; | |||||
private final ClientDetailsService clientDetailsService; | |||||
private final RedisTemplate redisTemplate; | |||||
private final TokenStore tokenStore; | |||||
private final CacheManager cacheManager; | |||||
/** | |||||
* 认证页面 | |||||
* @param modelAndView | |||||
* @param error 表单登录失败处理回调的错误信息 | |||||
* @return ModelAndView | |||||
*/ | |||||
@GetMapping("/login") | |||||
public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) { | |||||
modelAndView.setViewName("ftl/login"); | |||||
modelAndView.addObject("error", error); | |||||
return modelAndView; | |||||
} | |||||
/** | |||||
* 确认授权页面 | |||||
* @param request | |||||
* @param session | |||||
* @param modelAndView | |||||
* @return | |||||
*/ | |||||
@GetMapping("/confirm_access") | |||||
public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) { | |||||
Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes"); | |||||
modelAndView.addObject("scopeList", scopeList.keySet()); | |||||
Object auth = session.getAttribute("authorizationRequest"); | |||||
if (auth != null) { | |||||
AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth; | |||||
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); | |||||
modelAndView.addObject("app", clientDetails.getAdditionalInformation()); | |||||
modelAndView.addObject("user", SecurityUtils.getUser()); | |||||
} | |||||
modelAndView.setViewName("ftl/confirm"); | |||||
return modelAndView; | |||||
} | |||||
/** | |||||
* 退出token | |||||
* @param authHeader Authorization | |||||
*/ | |||||
@DeleteMapping("/logout") | |||||
public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) { | |||||
if (StrUtil.isBlank(authHeader)) { | |||||
return R.ok(Boolean.FALSE, "退出失败,token 为空"); | |||||
} | |||||
String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim(); | |||||
return delToken(tokenValue); | |||||
} | |||||
/** | |||||
* 令牌管理调用 | |||||
* @param token token | |||||
* @return | |||||
*/ | |||||
@Inner | |||||
@DeleteMapping("/{token}") | |||||
public R<Boolean> delToken(@PathVariable("token") String token) { | |||||
OAuth2AccessToken accessToken = tokenStore.readAccessToken(token); | |||||
if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) { | |||||
return R.ok(Boolean.TRUE, "退出失败,token 无效"); | |||||
} | |||||
OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken); | |||||
// 清空用户信息 | |||||
cacheManager.getCache(CacheConstants.USER_DETAILS).evict(auth2Authentication.getName()); | |||||
// 清空access token | |||||
tokenStore.removeAccessToken(accessToken); | |||||
// 清空 refresh token | |||||
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken(); | |||||
tokenStore.removeRefreshToken(refreshToken); | |||||
return R.ok(); | |||||
} | |||||
/** | |||||
* 查询token | |||||
* @param params 分页参数 | |||||
* @return | |||||
*/ | |||||
@Inner | |||||
@PostMapping("/page") | |||||
public R<Page> tokenList(@RequestBody Map<String, Object> params) { | |||||
// 根据分页参数获取对应数据 | |||||
String key = String.format("%s*:%s", PIGX_OAUTH_ACCESS, TenantContextHolder.getTenantId()); | |||||
List<String> pages = findKeysForPage(key, MapUtil.getInt(params, PaginationConstants.CURRENT), | |||||
MapUtil.getInt(params, PaginationConstants.SIZE)); | |||||
redisTemplate.setKeySerializer(new StringRedisSerializer()); | |||||
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); | |||||
Page result = new Page(MapUtil.getInt(params, PaginationConstants.CURRENT), | |||||
MapUtil.getInt(params, PaginationConstants.SIZE)); | |||||
result.setRecords(redisTemplate.opsForValue().multiGet(pages)); | |||||
result.setTotal(redisTemplate.keys(key).size()); | |||||
return R.ok(result); | |||||
} | |||||
private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) { | |||||
ScanOptions options = ScanOptions.scanOptions().count(1000L).match(patternKey).build(); | |||||
RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer(); | |||||
Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection( | |||||
redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize)); | |||||
List<String> result = new ArrayList<>(); | |||||
int tmpIndex = 0; | |||||
int startIndex = (pageNum - 1) * pageSize; | |||||
int end = pageNum * pageSize; | |||||
assert cursor != null; | |||||
while (cursor.hasNext()) { | |||||
if (tmpIndex >= startIndex && tmpIndex < end) { | |||||
result.add(cursor.next().toString()); | |||||
tmpIndex++; | |||||
continue; | |||||
} | |||||
if (tmpIndex >= end) { | |||||
break; | |||||
} | |||||
tmpIndex++; | |||||
cursor.next(); | |||||
} | |||||
try { | |||||
cursor.close(); | |||||
} | |||||
catch (IOException e) { | |||||
log.error("关闭cursor 失败"); | |||||
} | |||||
return result; | |||||
} | |||||
} |
@@ -0,0 +1,77 @@ | |||||
/* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
*/ | |||||
package com.pig4cloud.pigx.auth.handler; | |||||
import com.pig4cloud.pigx.admin.api.entity.SysLog; | |||||
import com.pig4cloud.pigx.admin.api.feign.RemoteLogService; | |||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants; | |||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants; | |||||
import com.pig4cloud.pigx.common.core.util.WebUtils; | |||||
import com.pig4cloud.pigx.common.log.util.SysLogUtils; | |||||
import com.pig4cloud.pigx.common.security.handler.AuthenticationFailureHandler; | |||||
import lombok.AllArgsConstructor; | |||||
import lombok.SneakyThrows; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.http.HttpHeaders; | |||||
import org.springframework.scheduling.annotation.Async; | |||||
import org.springframework.security.core.Authentication; | |||||
import org.springframework.security.core.AuthenticationException; | |||||
import org.springframework.stereotype.Component; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
/** | |||||
* @author lengleng | |||||
* @date 2018/10/8 | |||||
*/ | |||||
@Slf4j | |||||
@Component | |||||
@AllArgsConstructor | |||||
public class PigxAuthenticationFailureEventHandler implements AuthenticationFailureHandler { | |||||
private final RemoteLogService logService; | |||||
/** | |||||
* 异步处理,登录失败方法 | |||||
* <p> | |||||
* @param authenticationException 登录的authentication 对象 | |||||
* @param authentication 登录的authenticationException 对象 | |||||
* @param request 请求 | |||||
* @param response 响应 | |||||
*/ | |||||
@Async | |||||
@Override | |||||
@SneakyThrows | |||||
public void handle(AuthenticationException authenticationException, Authentication authentication, | |||||
HttpServletRequest request, HttpServletResponse response) { | |||||
String username = authentication.getName(); | |||||
SysLog sysLog = SysLogUtils.getSysLog(request, username); | |||||
sysLog.setTitle(username + "用户登录"); | |||||
sysLog.setType(CommonConstants.STATUS_LOCK); | |||||
sysLog.setParams(username); | |||||
sysLog.setException(authenticationException.getLocalizedMessage()); | |||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION); | |||||
sysLog.setServiceId(WebUtils.extractClientId(header).orElse("N/A")); | |||||
logService.saveLog(sysLog, SecurityConstants.FROM_IN); | |||||
log.info("用户:{} 登录失败,异常:{}", username, authenticationException.getLocalizedMessage()); | |||||
} | |||||
} |
@@ -0,0 +1,69 @@ | |||||
/* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
*/ | |||||
package com.pig4cloud.pigx.auth.handler; | |||||
import com.pig4cloud.pigx.admin.api.entity.SysLog; | |||||
import com.pig4cloud.pigx.admin.api.feign.RemoteLogService; | |||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants; | |||||
import com.pig4cloud.pigx.common.core.util.WebUtils; | |||||
import com.pig4cloud.pigx.common.log.util.SysLogUtils; | |||||
import com.pig4cloud.pigx.common.security.handler.AuthenticationSuccessHandler; | |||||
import lombok.AllArgsConstructor; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.http.HttpHeaders; | |||||
import org.springframework.scheduling.annotation.Async; | |||||
import org.springframework.security.core.Authentication; | |||||
import org.springframework.stereotype.Component; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
/** | |||||
* @author lengleng | |||||
* @date 2018/10/8 | |||||
*/ | |||||
@Slf4j | |||||
@Component | |||||
@AllArgsConstructor | |||||
public class PigxAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler { | |||||
private final RemoteLogService logService; | |||||
/** | |||||
* 处理登录成功方法 | |||||
* <p> | |||||
* 获取到登录的authentication 对象 | |||||
* @param authentication 登录对象 | |||||
* @param request 请求 | |||||
* @param response 返回 | |||||
*/ | |||||
@Async | |||||
@Override | |||||
public void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { | |||||
String username = authentication.getName(); | |||||
SysLog sysLog = SysLogUtils.getSysLog(request, username); | |||||
sysLog.setTitle(username + "用户登录"); | |||||
sysLog.setParams(username); | |||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION); | |||||
sysLog.setServiceId(WebUtils.extractClientId(header).orElse("N/A")); | |||||
logService.saveLog(sysLog, SecurityConstants.FROM_IN); | |||||
log.info("用户:{} 登录成功", username); | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
package com.pig4cloud.pigx.auth.service; | |||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants; | |||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants; | |||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; | |||||
import org.springframework.cache.annotation.Cacheable; | |||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException; | |||||
import org.springframework.security.oauth2.provider.ClientDetails; | |||||
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; | |||||
import org.springframework.stereotype.Service; | |||||
import javax.sql.DataSource; | |||||
/** | |||||
* @author lengleng | |||||
* @date 2020/03/25 | |||||
* <p> | |||||
* 扩展 JdbcClientDetailsService 支持多租户 | |||||
*/ | |||||
@Service | |||||
public class PigxClientDetailsServiceImpl extends JdbcClientDetailsService { | |||||
public PigxClientDetailsServiceImpl(DataSource dataSource) { | |||||
super(dataSource); | |||||
} | |||||
/** | |||||
* 重写原生方法支持redis缓存 | |||||
* @param clientId | |||||
* @return ClientDetails | |||||
* @throws InvalidClientException | |||||
*/ | |||||
@Override | |||||
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null") | |||||
public ClientDetails loadClientByClientId(String clientId) { | |||||
super.setSelectClientDetailsSql( | |||||
String.format(SecurityConstants.DEFAULT_SELECT_STATEMENT, TenantContextHolder.getTenantId())); | |||||
return super.loadClientByClientId(clientId); | |||||
} | |||||
} |
@@ -0,0 +1,123 @@ | |||||
/* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
*/ | |||||
package com.pig4cloud.pigx.auth.service; | |||||
import cn.hutool.core.util.ArrayUtil; | |||||
import cn.hutool.core.util.StrUtil; | |||||
import com.pig4cloud.pigx.admin.api.dto.UserInfo; | |||||
import com.pig4cloud.pigx.admin.api.entity.SysUser; | |||||
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService; | |||||
import com.pig4cloud.pigx.common.core.constant.CacheConstants; | |||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants; | |||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants; | |||||
import com.pig4cloud.pigx.common.core.util.R; | |||||
import com.pig4cloud.pigx.common.security.service.PigxUser; | |||||
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService; | |||||
import lombok.AllArgsConstructor; | |||||
import lombok.SneakyThrows; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.cache.Cache; | |||||
import org.springframework.cache.CacheManager; | |||||
import org.springframework.security.core.GrantedAuthority; | |||||
import org.springframework.security.core.authority.AuthorityUtils; | |||||
import org.springframework.security.core.userdetails.UserDetails; | |||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; | |||||
import org.springframework.stereotype.Service; | |||||
import java.util.Arrays; | |||||
import java.util.Collection; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
/** | |||||
* 用户详细信息 | |||||
* | |||||
* @author lengleng | |||||
*/ | |||||
@Slf4j | |||||
@Service | |||||
@AllArgsConstructor | |||||
public class PigxUserDetailsServiceImpl implements PigxUserDetailsService { | |||||
private final RemoteUserService remoteUserService; | |||||
private final CacheManager cacheManager; | |||||
/** | |||||
* 用户密码登录 | |||||
* @param username 用户名 | |||||
* @return | |||||
* @throws UsernameNotFoundException | |||||
*/ | |||||
@Override | |||||
@SneakyThrows | |||||
public UserDetails loadUserByUsername(String username) { | |||||
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); | |||||
if (cache != null && cache.get(username) != null) { | |||||
return (PigxUser) cache.get(username).get(); | |||||
} | |||||
R<UserInfo> result = remoteUserService.info(username, SecurityConstants.FROM_IN); | |||||
UserDetails userDetails = getUserDetails(result); | |||||
cache.put(username, userDetails); | |||||
return userDetails; | |||||
} | |||||
/** | |||||
* 根据社交登录code 登录 | |||||
* @param inStr TYPE@CODE | |||||
* @return UserDetails | |||||
* @throws UsernameNotFoundException | |||||
*/ | |||||
@Override | |||||
@SneakyThrows | |||||
public UserDetails loadUserBySocial(String inStr) { | |||||
return getUserDetails(remoteUserService.social(inStr, SecurityConstants.FROM_IN)); | |||||
} | |||||
/** | |||||
* 构建userdetails | |||||
* @param result 用户信息 | |||||
* @return | |||||
*/ | |||||
private UserDetails getUserDetails(R<UserInfo> result) { | |||||
if (result == null || result.getData() == null) { | |||||
throw new UsernameNotFoundException("用户不存在"); | |||||
} | |||||
UserInfo info = result.getData(); | |||||
Set<String> dbAuthsSet = new HashSet<>(); | |||||
if (ArrayUtil.isNotEmpty(info.getRoles())) { | |||||
// 获取角色 | |||||
Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId)); | |||||
// 获取资源 | |||||
dbAuthsSet.addAll(Arrays.asList(info.getPermissions())); | |||||
} | |||||
Collection<? extends GrantedAuthority> authorities = AuthorityUtils | |||||
.createAuthorityList(dbAuthsSet.toArray(new String[0])); | |||||
SysUser user = info.getSysUser(); | |||||
boolean enabled = StrUtil.equals(user.getLockFlag(), CommonConstants.STATUS_NORMAL); | |||||
// 构造security用户 | |||||
return new PigxUser(user.getUserId(), user.getDeptId(), user.getPhone(), user.getAvatar(), user.getTenantId(), | |||||
user.getUsername(), SecurityConstants.BCRYPT + user.getPassword(), enabled, true, true, | |||||
!CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities); | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
server: | |||||
port: 3000 | |||||
spring: | |||||
application: | |||||
name: @artifactId@ | |||||
cloud: | |||||
nacos: | |||||
discovery: | |||||
server-addr: ${NACOS_HOST:pigx-register}:${NACOS_PORT:8848} | |||||
config: | |||||
server-addr: ${spring.cloud.nacos.discovery.server-addr} | |||||
file-extension: yml | |||||
shared-configs: | |||||
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} | |||||
profiles: | |||||
active: @profiles.active@ |
@@ -0,0 +1,87 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!-- | |||||
~ Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
--> | |||||
<!-- | |||||
小技巧: 在根pom里面设置统一存放路径,统一管理方便维护 | |||||
<properties> | |||||
<log-path>/Users/lengleng</log-path> | |||||
</properties> | |||||
1. 其他模块加日志输出,直接copy本文件放在resources 目录即可 | |||||
2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块 | |||||
--> | |||||
<configuration debug="false" scan="false"> | |||||
<property name="log.path" value="logs/${project.artifactId}"/> | |||||
<!-- 彩色日志格式 --> | |||||
<property name="CONSOLE_LOG_PATTERN" | |||||
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> | |||||
<!-- 彩色日志依赖的渲染类 --> | |||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> | |||||
<conversionRule conversionWord="wex" | |||||
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/> | |||||
<conversionRule conversionWord="wEx" | |||||
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> | |||||
<!-- Console log output --> | |||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender"> | |||||
<encoder> | |||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern> | |||||
</encoder> | |||||
</appender> | |||||
<!-- Log file debug output --> | |||||
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender"> | |||||
<file>${log.path}/debug.log</file> | |||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | |||||
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> | |||||
<maxFileSize>50MB</maxFileSize> | |||||
<maxHistory>30</maxHistory> | |||||
</rollingPolicy> | |||||
<encoder> | |||||
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern> | |||||
</encoder> | |||||
</appender> | |||||
<!-- Log file error output --> | |||||
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender"> | |||||
<file>${log.path}/error.log</file> | |||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | |||||
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> | |||||
<maxFileSize>50MB</maxFileSize> | |||||
<maxHistory>30</maxHistory> | |||||
</rollingPolicy> | |||||
<encoder> | |||||
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern> | |||||
</encoder> | |||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | |||||
<level>ERROR</level> | |||||
</filter> | |||||
</appender> | |||||
<logger name="org.activiti.engine.impl.db" level="DEBUG"> | |||||
<appender-ref ref="debug"/> | |||||
</logger> | |||||
<!--nacos 心跳 INFO 屏蔽--> | |||||
<logger name="com.alibaba.nacos" level="OFF"> | |||||
<appender-ref ref="error"/> | |||||
</logger> | |||||
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 --> | |||||
<root level="INFO"> | |||||
<appender-ref ref="console"/> | |||||
<appender-ref ref="debug"/> | |||||
</root> | |||||
</configuration> |
@@ -0,0 +1,67 @@ | |||||
/* | |||||
* Copyright (c) 2018-2025, lengleng 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 pig4cloud.com 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: lengleng (wangiegie@gmail.com) | |||||
*/ | |||||
.sign_body { | |||||
padding-top: 40px; | |||||
padding-bottom: 40px; | |||||
background-color: #eee; | |||||
} | |||||
.form-signin { | |||||
max-width: 330px; | |||||
padding: 15px; | |||||
margin: 0 auto; | |||||
} | |||||
.form-margin-top { | |||||
margin-top: 50px; | |||||
} | |||||
.form-signin .form-signin-heading, | |||||
.form-signin .checkbox { | |||||
margin-bottom: 10px; | |||||
} | |||||
.form-signin .checkbox { | |||||
font-weight: normal; | |||||
} | |||||
.form-signin .form-control { | |||||
position: relative; | |||||
height: auto; | |||||
-webkit-box-sizing: border-box; | |||||
-moz-box-sizing: border-box; | |||||
box-sizing: border-box; | |||||
padding: 10px; | |||||
font-size: 16px; | |||||
} | |||||
.form-signin .form-control:focus { | |||||
z-index: 2; | |||||
} | |||||
.form-signin input[type="email"] { | |||||
margin-bottom: -1px; | |||||
border-bottom-right-radius: 0; | |||||
border-bottom-left-radius: 0; | |||||
} | |||||
.form-signin input[type="password"] { | |||||
margin-bottom: 10px; | |||||
border-top-left-radius: 0; | |||||
border-top-right-radius: 0; | |||||
} | |||||
footer{ | |||||
text-align: center; | |||||
position:absolute; | |||||
bottom:0; | |||||
width:100%; | |||||
height:100px; | |||||
} |
@@ -0,0 +1,51 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<html> | |||||
<head> | |||||
<meta charset="UTF-8"/> | |||||
<meta name="viewport" | |||||
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/> | |||||
<title>PigX第三方授权</title> | |||||
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/> | |||||
<link rel="stylesheet" type="text/css" href="/css/signin.css"/> | |||||
</head> | |||||
<body> | |||||
<nav class="navbar navbar-default container-fluid"> | |||||
<div class="container"> | |||||
<div class="navbar-header"> | |||||
<a class="navbar-brand" href="#">开放平台</a> | |||||
</div> | |||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-5"> | |||||
<p class="navbar-text navbar-right"> | |||||
<a target="_blank" href="https://pig4cloud.com">技术支持</a> | |||||
</p> | |||||
<p class="navbar-text navbar-right"> | |||||
<a target="_blank" href="https://pig4cloud.com">${user.username}</a> | |||||
</p> | |||||
</div> | |||||
</div> | |||||
</nav> | |||||
<div style="padding-top: 80px;width: 300px; color: #555; margin:0px auto;"> | |||||
<form id='confirmationForm' name='confirmationForm' action="/oauth/authorize" method='post'> | |||||
<input name='user_oauth_approval' value='true' type='hidden'/> | |||||
<p> | |||||
<a href="${app.website!''}" target="_blank">${app.appName!'未定义应用名称'}</a> 将获得以下权限:</p> | |||||
<ul class="list-group"> | |||||
<li class="list-group-item"> <span> | |||||
<#list scopeList as scope> | |||||
<input type="hidden" name="${scope}" value="true"/> | |||||
<input type="checkbox" disabled checked="checked"/><label>${scope}</label> | |||||
</#list> | |||||
</ul> | |||||
<p class="help-block">授权后表明你已同意 <a>服务协议</a></p> | |||||
<button class="btn btn-success pull-right" type="submit" id="write-email-btn">授权</button> | |||||
</p> | |||||
</form> | |||||
</div> | |||||
<footer> | |||||
<p>support by: pig4cloud.com</p> | |||||
<p>email: <a href="mailto:wangiegie@gmail.com">wangiegie@gmail.com</a>.</p> | |||||
</footer> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,34 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> | |||||
<meta name="description" content=""> | |||||
<meta name="author" content=""> | |||||
<title>PigX微服务统一认证</title> | |||||
<link href="/css/bootstrap.min.css" rel="stylesheet"> | |||||
<link href="/css/signin.css" rel="stylesheet"> | |||||
</head> | |||||
<body class="sign_body"> | |||||
<div class="container form-margin-top"> | |||||
<form class="form-signin" action="/token/form" method="post"> | |||||
<h2 class="form-signin-heading" align="center">统一认证系统</h2> | |||||
<input type="text" name="username" class="form-control form-margin-top" placeholder="账号" required autofocus> | |||||
<input type="password" name="password" class="form-control" placeholder="密码" required> | |||||
<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button> | |||||
<#if error??> | |||||
<span style="color: red; ">${error}</span> | |||||
</#if> | |||||
</form> | |||||
</div> | |||||
<footer> | |||||
<p>support by: pig4cloud</p> | |||||
<p>email: <a href="mailto:pig4cloud@qq.com">pig4cloud@qq.com</a>.</p> | |||||
</footer> | |||||
</body> | |||||
</html> |