Spring?Clou整合?Security?+?Oauth2?+?jwt實(shí)現(xiàn)權(quán)限認(rèn)證的詳細(xì)過程
前言
OAuth2是一個(gè)開放的標(biāo)準(zhǔn),協(xié)議。即允許用戶讓第三方應(yīng)用訪問某一個(gè)網(wǎng)站上存儲(chǔ)的用戶私密資源(照片,頭像等)。這個(gè)過程中無需將用戶名和密碼提供給第三方應(yīng)用。在互聯(lián)網(wǎng)中,我們最常見的OAuth2的應(yīng)用就是各種第三方通過QQ授權(quán),微信授權(quán),微博授權(quán)等登錄了。
OAuth2協(xié)議一共支持4中不同的授權(quán)模式。
- 授權(quán)碼模式:常見的第三方平臺(tái)登錄功能基本上都使用這種模式。
- 簡化模式:簡化模式不想要客戶端服務(wù)器(第三方應(yīng)用服務(wù)器)參與,直接在瀏覽器中向授權(quán)服務(wù)器申請(qǐng)令牌。
- 密碼模式:密碼模式是用戶把用戶名,密碼直接告訴客戶端,客戶端使用這些信息向授權(quán)服務(wù)器申請(qǐng)令牌。這需要用戶對(duì)客戶端高度信任。
- 客戶端模式:客戶端模式是指客戶端使用自己的名義而不是用戶的名義向服務(wù)器申請(qǐng)授權(quán)。
OAuth2的重要參數(shù)
①response_type
code:表示要求返回授權(quán)碼。token:表示直接返回令牌
②client_id
客戶端身份標(biāo)識(shí)
③client_secret
客戶端密鑰
④redirect_uri
重定向地址
⑤scope
表示授權(quán)的范圍。read:只讀權(quán)限,all讀寫權(quán)限
⑥grant_type
表示授權(quán)的方式。AUTHORIZATION_CODE(授權(quán)碼),PASSWORD(密碼),CLIENT_CREDENTIALS(品正式),REFRESH_TOKEN(更新令牌)
⑦state
應(yīng)用程序傳遞的一個(gè)隨機(jī)數(shù),用來防止CSRF攻擊
話不多說直接上代碼
一、創(chuàng)建項(xiàng)目
首先創(chuàng)建一個(gè)主體:
SpringSecurityOauth2jtw
子項(xiàng)目:
admin——登錄
oauth2——權(quán)限
common——公共
gateway——網(wǎng)關(guān)
二、步驟
1.引入依賴
代碼如下(示例):
主體依賴:
<java.version>1.8</java.version>
<spring-boot.version>2.7.0</spring-boot.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
<spring-cloud-starter-oauth2.version>2.2.5.RELEASE</spring-cloud-starter-oauth2.version>
<!--Mysql數(shù)據(jù)庫驅(qū)動(dòng)-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!--SpringData工具包-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${spring-data-commons.version}</version>
</dependency>
<!--JWT(Json Web Token)登錄支持-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!--JWT(Json Web Token)登錄支持-->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>${nimbus-jose-jwt.version}</version>
</dependency>
<!--集成druid連接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--Hutool Java工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--Spring Cloud 相關(guān)依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Cloud Alibaba 相關(guān)依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>common依賴導(dǎo)入:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>oauth2依賴導(dǎo)入:
<dependency>
<groupId>com.common</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>${spring-cloud-starter-oauth2.version}</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>admin依賴導(dǎo)入:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.common</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>2.gateway代碼部分
代碼如下(示例):
配置鑒權(quán)管理器
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
URI uri = request.getURI();
PathMatcher pathMatcher = new AntPathMatcher();
//白名單路徑直接放行
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
for (String ignoreUrl : ignoreUrls) {
if (pathMatcher.match(ignoreUrl, uri.getPath())) {
return Mono.just(new AuthorizationDecision(true));
}
}
//對(duì)應(yīng)跨域的預(yù)檢請(qǐng)求直接放行
if(request.getMethod()==HttpMethod.OPTIONS){
return Mono.just(new AuthorizationDecision(true));
}
//不同用戶體系登錄不允許互相訪問
try {
String token = request.getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER);
if(StrUtil.isEmpty(token)){
return Mono.just(new AuthorizationDecision(false));
}
String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");
JWSObject jwsObject = JWSObject.parse(realToken);
String userStr = jwsObject.getPayload().toString();
UserDto userDto = JSONUtil.toBean(userStr, UserDto.class);
if (AuthConstant.ADMIN_CLIENT_ID.equals(userDto.getClientId()) && !pathMatcher.match(AuthConstant.ADMIN_URL_PATTERN, uri.getPath())) {
return Mono.just(new AuthorizationDecision(false));
}
if (AuthConstant.PORTAL_CLIENT_ID.equals(userDto.getClientId()) && pathMatcher.match(AuthConstant.ADMIN_URL_PATTERN, uri.getPath())) {
return Mono.just(new AuthorizationDecision(false));
}
} catch (ParseException e) {
e.printStackTrace();
return Mono.just(new AuthorizationDecision(false));
}
//非管理端路徑直接放行
if (!pathMatcher.match(AuthConstant.ADMIN_URL_PATTERN, uri.getPath())) {
return Mono.just(new AuthorizationDecision(true));
}
//管理端路徑需校驗(yàn)權(quán)限
Map<Object, Object> resourceRolesMap = redisTemplate.opsForHash().entries(AuthConstant.RESOURCE_ROLES_MAP_KEY);
Iterator<Object> iterator = resourceRolesMap.keySet().iterator();
List<String> authorities = new ArrayList<>();
while (iterator.hasNext()) {
String pattern = (String) iterator.next();
if (pathMatcher.match(pattern, uri.getPath())) {
authorities.addAll(Convert.toList(String.class, resourceRolesMap.get(pattern)));
}
}
authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
//認(rèn)證通過且角色匹配的用戶可訪問當(dāng)前路徑
return mono
.filter(Authentication::isAuthenticated)
.flatMapIterable(Authentication::getAuthorities)
.map(GrantedAuthority::getAuthority)
.any(authorities::contains)
.map(AuthorizationDecision::new)
.defaultIfEmpty(new AuthorizationDecision(false));
}未登錄或者token失效時(shí)的返回結(jié)果:
@Component
public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin","*");
response.getHeaders().set("Cache-Control","no-cache");
String body= JSONUtil.toJsonStr(CommonResult.unauthorized(e.getMessage()));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}
}無權(quán)限訪問的返回結(jié)果:
@Component
public class RestfulAccessDeniedHandler implements ServerAccessDeniedHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin","*");
response.getHeaders().set("Cache-Control","no-cache");
String body= JSONUtil.toJsonStr(CommonResult.forbidden(denied.getMessage()));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}
}全局跨域配置:注意:前端從網(wǎng)關(guān)進(jìn)行調(diào)用時(shí)需要配置
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}網(wǎng)關(guān)白名單配置:
@Data
@EqualsAndHashCode(callSuper = false)
@Component
@ConfigurationProperties(prefix="secure.ignore")
public class IgnoreUrlsConfig {
private List<String> urls;
}資源服務(wù)器配置:
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
private final AuthorizationManager authorizationManager;
private final IgnoreUrlsConfig ignoreUrlsConfig;
private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
//自定義處理JWT請(qǐng)求頭過期或簽名錯(cuò)誤的結(jié)果
http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
//對(duì)白名單路徑,直接移除JWT請(qǐng)求頭
http.addFilterBefore(ignoreUrlsRemoveJwtFilter,SecurityWebFiltersOrder.AUTHENTICATION);
http.authorizeExchange()
//白名單配置
.pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()
//鑒權(quán)管理器配置
.anyExchange().access(authorizationManager)
.and().exceptionHandling()
//處理未授權(quán)
.accessDeniedHandler(restfulAccessDeniedHandler)
//處理未認(rèn)證
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and().csrf().disable();
return http.build();
}
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}Swagger資源配置:
@Slf4j
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//獲取所有路由的ID
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//過濾出配置文件中定義的路由->過濾出Path Route Predicate->根據(jù)路徑拼接成api-docs路徑->生成SwaggerResource
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs"))));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
log.info("name:{},location:{}", name, location);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}自定義Swagger的各個(gè)配置節(jié)點(diǎn):
@RestController
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
/**
* Swagger安全配置,支持oauth和apiKey設(shè)置
*/
@GetMapping("/swagger-resources/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
/**
* Swagger UI配置
*/
@GetMapping("/swagger-resources/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
/**
* Swagger資源配置,微服務(wù)中這各個(gè)服務(wù)的api-docs信息
*/
@GetMapping("/swagger-resources")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}將登錄用戶的JWT轉(zhuǎn)化成用戶信息的全局過濾器:
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER);
if (StrUtil.isEmpty(token)) {
return chain.filter(exchange);
}
try {
//從token中解析用戶信息并設(shè)置到Header中去
String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");
JWSObject jwsObject = JWSObject.parse(realToken);
String userStr = jwsObject.getPayload().toString();
LOGGER.info("AuthGlobalFilter.filter() user:{}",userStr);
ServerHttpRequest request = exchange.getRequest().mutate().header(AuthConstant.USER_TOKEN_HEADER, userStr).build();
exchange = exchange.mutate().request(request).build();
} catch (ParseException e) {
e.printStackTrace();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}白名單路徑訪問時(shí)移除JWT請(qǐng)求頭的過濾器:
@Component
public class IgnoreUrlsRemoveJwtFilter implements WebFilter {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
PathMatcher pathMatcher = new AntPathMatcher();
//白名單路徑移除JWT請(qǐng)求頭
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
for (String ignoreUrl : ignoreUrls) {
if (pathMatcher.match(ignoreUrl, uri.getPath())) {
request = exchange.getRequest().mutate().header(AuthConstant.JWT_TOKEN_HEADER, "").build();
exchange = exchange.mutate().request(request).build();
return chain.filter(exchange);
}
}
return chain.filter(exchange);
}
}yml配置:
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true #使用小寫service-id
routes: #配置路由路徑
- id: oauth
uri: lb://oauth
predicates:
- Path=/oauth/**
filters:
- StripPrefix=1
- id: admin
uri: lb://admin #這里是配置的nacos
predicates:
- Path=/admin/**
filters:
- StripPrefix=1
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: 'http://localhost:8201/oauth/rsa/publicKey' #配置RSA的公鑰訪問地址
redis:
host: localhost # Redis服務(wù)器地址
database: 0 # Redis數(shù)據(jù)庫索引(默認(rèn)為0)
port: 6379 # Redis服務(wù)器連接端口
password: # Redis服務(wù)器連接密碼(默認(rèn)為空)
timeout: 3000ms # 連接超時(shí)時(shí)間(毫秒)
secure:
ignore:
urls: #配置白名單路徑
- "/doc.html"
- "/swagger-resources/**"
- "/swagger/**"
- "/*/v2/api-docs"
- "/*/*.js"
- "/*/*.css"
- "/*/*.png"
- "/*/*.ico"
- "/webjars/**"
- "/actuator/**"
- "/oauth/oauth/token"
- "/oauth/rsa/publicKey"
- "/admin/admin/login"
- "/admin/admin/register"
management: #開啟SpringBoot Admin的監(jiān)控
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
logging:
level:
root: info
com.macro.mall: debug
logstash:
host: localhost
#spring:
# application:
# name: gateway
# cloud:
# nacos:
# config:
# server-addr: 127.0.0.1:8848
# username: nacos
# password: nacos
# namespace: 4e903430-c64b-4c68-a43c-59478dd173e6
# group: DEFAULT_GROUP
# prefix: ${spring.application.name}
# file-extension: yaml3.oauth2代碼部分
JWT內(nèi)容增強(qiáng)器:
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
Map<String, Object> info = new HashMap<>();
//把用戶ID設(shè)置到JWT中
info.put("id", securityUser.getId());
info.put("client_id",securityUser.getClientId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}認(rèn)證服務(wù)器配置:
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final UserServiceImpl userDetailsService;
private final AuthenticationManager authenticationManager;
private final JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("admin-app")
.secret(passwordEncoder.encode("123456"))
.scopes("all")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(3600*24)
.refreshTokenValiditySeconds(3600*24*7)
.and()
.withClient("portal-app")
.secret(passwordEncoder.encode("123456"))
.scopes("all")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(3600*24)
.refreshTokenValiditySeconds(3600*24*7);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(delegates); //配置JWT的內(nèi)容增強(qiáng)器
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService) //配置加載用戶信息的服務(wù)
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(enhancerChain);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair());
return jwtAccessTokenConverter;
}
@Bean
public KeyPair keyPair() {
//從classpath下的證書中獲取秘鑰對(duì)
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
}Swagger相關(guān)配置:
@Configuration
@EnableSwagger2
public class SwaggerConfig extends BaseSwaggerConfig {
@Override
public SwaggerProperties swaggerProperties() {
return SwaggerProperties.builder()
.apiBasePackage("com.oauth.oauth2.controller")
.title("認(rèn)證中心")
.description("認(rèn)證中心相關(guān)接口文檔")
.contactName("oauth")
.version("1.0")
.enableSecurity(true)
.build();
}
@Bean
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return generateBeanPostProcessor();
}
}SpringSecurity相關(guān)配置:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig{
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}消息常量定義:
public class MessageConstant {
public static final String LOGIN_SUCCESS = "登錄成功!";
public static final String USERNAME_PASSWORD_ERROR = "用戶名或密碼錯(cuò)誤!";
public static final String CREDENTIALS_EXPIRED = "該賬戶的登錄憑證已過期,請(qǐng)重新登錄!";
public static final String ACCOUNT_DISABLED = "該賬戶已被禁用,請(qǐng)聯(lián)系管理員!";
public static final String ACCOUNT_LOCKED = "該賬號(hào)已被鎖定,請(qǐng)聯(lián)系管理員!";
public static final String ACCOUNT_EXPIRED = "該賬號(hào)已過期,請(qǐng)聯(lián)系管理員!";
public static final String PERMISSION_DENIED = "沒有訪問權(quán)限,請(qǐng)聯(lián)系管理員!";
}自定義Oauth2獲取令牌接口:
@RestController
@Api(tags = "AuthController", description = "認(rèn)證中心登錄認(rèn)證")
@RequestMapping("/oauth")
public class AuthController {
@Autowired
private TokenEndpoint tokenEndpoint;
@ApiOperation("Oauth2獲取token")
@RequestMapping(value = "/token", method = RequestMethod.POST)
public CommonResult<Oauth2TokenDto> postAccessToken(HttpServletRequest request,
@ApiParam("授權(quán)模式") @RequestParam String grant_type,
@ApiParam("Oauth2客戶端ID") @RequestParam String client_id,
@ApiParam("Oauth2客戶端秘鑰") @RequestParam String client_secret,
@ApiParam("刷新token") @RequestParam(required = false) String refresh_token,
@ApiParam("登錄用戶名") @RequestParam(required = false) String username,
@ApiParam("登錄密碼") @RequestParam(required = false) String password) throws HttpRequestMethodNotSupportedException {
Map<String, String> parameters = new HashMap<>();
parameters.put("grant_type",grant_type);
parameters.put("client_id",client_id);
parameters.put("client_secret",client_secret);
parameters.putIfAbsent("refresh_token",refresh_token);
parameters.putIfAbsent("username",username);
parameters.putIfAbsent("password",password);
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(request.getUserPrincipal(), parameters).getBody();
Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.tokenHead(AuthConstant.JWT_TOKEN_PREFIX).build();
return CommonResult.success(oauth2TokenDto);
}
}獲取RSA公鑰接口:
@RestController
@Api(tags = "KeyPairController", description = "獲取RSA公鑰接口")
@RequestMapping("/rsa")
public class KeyPairController {
@Autowired
private KeyPair keyPair;
@GetMapping("/publicKey")
public Map<String, Object> getKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}Oauth2獲取Token返回信息封裝:
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class Oauth2TokenDto {
@ApiModelProperty("訪問令牌")
private String token;
@ApiModelProperty("刷令牌")
private String refreshToken;
@ApiModelProperty("訪問令牌頭前綴")
private String tokenHead;
@ApiModelProperty("有效時(shí)間(秒)")
private int expiresIn;
}登錄用戶信息:
@Data
public class SecurityUser implements UserDetails {
/**
* ID
*/
private Long id;
/**
* 用戶名
*/
private String username;
/**
* 用戶密碼
*/
private String password;
/**
* 用戶狀態(tài)
*/
private Boolean enabled;
/**
* 登錄客戶端ID
*/
private String clientId;
/**
* 權(quán)限數(shù)據(jù)
*/
private Collection<SimpleGrantedAuthority> authorities;
public SecurityUser() {
}
public SecurityUser(UserDto userDto) {
this.setId(userDto.getId());
this.setUsername(userDto.getUsername());
this.setPassword(userDto.getPassword());
this.setEnabled(userDto.getStatus() == 1);
this.setClientId(userDto.getClientId());
if (userDto.getRoles() != null) {
authorities = new ArrayList<>();
userDto.getRoles().forEach(item -> authorities.add(new SimpleGrantedAuthority(item)));
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}全局處理Oauth2拋出的異常:
@ControllerAdvice
public class Oauth2ExceptionHandler {
@ResponseBody
@ExceptionHandler(value = OAuth2Exception.class)
public CommonResult handleOauth2(OAuth2Exception e) {
return CommonResult.failed(e.getMessage());
}
}后臺(tái)用戶服務(wù)遠(yuǎn)程調(diào)用Service:
@FeignClient("admin")
public interface UmsAdminService {
@GetMapping("/admin/loadByUsername")
UserDto loadUserByUsername(@RequestParam String username);
}用戶管理業(yè)務(wù)類:
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private UmsAdminService adminService;
@Autowired
private HttpServletRequest request;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String clientId = request.getParameter("client_id");
UserDto userDto = null;
if(AuthConstant.ADMIN_CLIENT_ID.equals(clientId)){
userDto = adminService.loadUserByUsername(username);
}
if (userDto==null) {
throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
}
userDto.setClientId(clientId);
SecurityUser securityUser = new SecurityUser(userDto);
if (!securityUser.isEnabled()) {
throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
} else if (!securityUser.isAccountNonLocked()) {
throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
} else if (!securityUser.isAccountNonExpired()) {
throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
} else if (!securityUser.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
}
return securityUser;
}
}yml配置:
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
management:
endpoints:
web:
exposure:
include: "*"
feign:
okhttp:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
logging:
level:
root: info
#spring:
# application:
# name: oauth
# cloud:
# nacos:
# config:
# server-addr: 127.0.0.1:8848
# username: nacos
# password: nacos
# namespace: 4e903430-c64b-4c68-a43c-59478dd173e6
# group: DEFAULT_GROUP
# prefix: ${spring.application.name}
# file-extension: yamljwt.jks需要自己去生成
4.common代碼部分
通用返回對(duì)象:
public class CommonResult<T> {
private long code;
private String message;
private T data;
protected CommonResult() {
}
protected CommonResult(long code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 成功返回結(jié)果
*
* @param data 獲取的數(shù)據(jù)
*/
public static <T> CommonResult<T> success(T data) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 成功返回結(jié)果
*
* @param data 獲取的數(shù)據(jù)
* @param message 提示信息
*/
public static <T> CommonResult<T> success(T data, String message) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 失敗返回結(jié)果
* @param errorCode 錯(cuò)誤碼
*/
public static <T> CommonResult<T> failed(IErrorCode errorCode) {
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
}
/**
* 失敗返回結(jié)果
* @param errorCode 錯(cuò)誤碼
* @param message 錯(cuò)誤信息
*/
public static <T> CommonResult<T> failed(IErrorCode errorCode,String message) {
return new CommonResult<T>(errorCode.getCode(), message, null);
}
/**
* 失敗返回結(jié)果
* @param message 提示信息
*/
public static <T> CommonResult<T> failed(String message) {
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
}
/**
* 失敗返回結(jié)果
*/
public static <T> CommonResult<T> failed() {
return failed(ResultCode.FAILED);
}
/**
* 參數(shù)驗(yàn)證失敗返回結(jié)果
*/
public static <T> CommonResult<T> validateFailed() {
return failed(ResultCode.VALIDATE_FAILED);
}
/**
* 參數(shù)驗(yàn)證失敗返回結(jié)果
* @param message 提示信息
*/
public static <T> CommonResult<T> validateFailed(String message) {
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
}
/**
* 未登錄返回結(jié)果
*/
public static <T> CommonResult<T> unauthorized(T data) {
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
}
/**
* 未授權(quán)返回結(jié)果
*/
public static <T> CommonResult<T> forbidden(T data) {
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
}
public long getCode() {
return code;
}
public void setCode(long code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}封裝API的錯(cuò)誤碼:
public interface IErrorCode {
long getCode();
String getMessage();
}枚舉了一些常用API操作碼:
public enum ResultCode implements IErrorCode {
SUCCESS(200, "操作成功"),
FAILED(500, "操作失敗"),
VALIDATE_FAILED(404, "參數(shù)檢驗(yàn)失敗"),
UNAUTHORIZED(401, "暫未登錄或token已經(jīng)過期"),
FORBIDDEN(403, "沒有相關(guān)權(quán)限");
private long code;
private String message;
private ResultCode(long code, String message) {
this.code = code;
this.message = message;
}
public long getCode() {
return code;
}
public String getMessage() {
return message;
}
}Redis基礎(chǔ)配置:
public class BaseRedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<Object> serializer = redisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public RedisSerializer<Object> redisSerializer() {
//創(chuàng)建JSON序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//必須設(shè)置,否則無法將JSON轉(zhuǎn)化為對(duì)象,會(huì)轉(zhuǎn)化成Map類型
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//設(shè)置Redis緩存有效期為1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
@Bean
public RedisService redisService(){
return new RedisServiceImpl();
}
}Swagger基礎(chǔ)配置:
public abstract class BaseSwaggerConfig {
@Bean
public Docket createRestApi() {
SwaggerProperties swaggerProperties = swaggerProperties();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo(swaggerProperties))
.select()
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getApiBasePackage()))
.paths(PathSelectors.any())
.build();
if (swaggerProperties.isEnableSecurity()) {
docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());
}
return docket;
}
private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
return new ApiInfoBuilder()
.title(swaggerProperties.getTitle())
.description(swaggerProperties.getDescription())
.contact(new Contact(swaggerProperties.getContactName(), swaggerProperties.getContactUrl(), swaggerProperties.getContactEmail()))
.version(swaggerProperties.getVersion())
.build();
}
private List<SecurityScheme> securitySchemes() {
//設(shè)置請(qǐng)求頭信息
List<SecurityScheme> result = new ArrayList<>();
ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
result.add(apiKey);
return result;
}
private List<SecurityContext> securityContexts() {
//設(shè)置需要登錄認(rèn)證的路徑
List<SecurityContext> result = new ArrayList<>();
result.add(getContextByPath("/*/.*"));
return result;
}
private SecurityContext getContextByPath(String pathRegex) {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex(pathRegex))
.build();
}
private List<SecurityReference> defaultAuth() {
List<SecurityReference> result = new ArrayList<>();
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
result.add(new SecurityReference("Authorization", authorizationScopes));
return result;
}
public BeanPostProcessor generateBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
/**
* 自定義Swagger配置
*/
public abstract SwaggerProperties swaggerProperties();
}權(quán)限相關(guān)常量定義:
public interface AuthConstant {
/**
* JWT存儲(chǔ)權(quán)限前綴
*/
String AUTHORITY_PREFIX = "ROLE_";
/**
* JWT存儲(chǔ)權(quán)限屬性
*/
String AUTHORITY_CLAIM_NAME = "authorities";
/**
* 后臺(tái) client_id
*/
String ADMIN_CLIENT_ID = "admin-app";
/**
* app client_id
*/
String PORTAL_CLIENT_ID = "portal-app";
/**
* 后臺(tái)接口路徑匹配
*/
String ADMIN_URL_PATTERN = "/admin/**";
/**
* Redis緩存權(quán)限規(guī)則key
*/
String RESOURCE_ROLES_MAP_KEY = "auth:resourceRolesMap";
/**
* 認(rèn)證信息Http請(qǐng)求頭
*/
String JWT_TOKEN_HEADER = "Authorization";
/**
* JWT令牌前綴
*/
String JWT_TOKEN_PREFIX = "Bearer ";
/**
* 用戶信息Http請(qǐng)求頭
*/
String USER_TOKEN_HEADER = "user";
}Swagger自定義配置:
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class SwaggerProperties {
/**
* API文檔生成基礎(chǔ)路徑
*/
private String apiBasePackage;
/**
* 是否要啟用登錄認(rèn)證
*/
private boolean enableSecurity;
/**
* 文檔標(biāo)題
*/
private String title;
/**
* 文檔描述
*/
private String description;
/**
* 文檔版本
*/
private String version;
/**
* 文檔聯(lián)系人姓名
*/
private String contactName;
/**
* 文檔聯(lián)系人網(wǎng)址
*/
private String contactUrl;
/**
* 文檔聯(lián)系人郵箱
*/
private String contactEmail;
}登錄用戶信息:
@Data
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
public class UserDto {
private Long id;
private String username;
private String password;
private Integer status;
private String clientId;
private List<String> roles;
}Controller層的日志封裝類:
@Data
@EqualsAndHashCode(callSuper = false)
public class WebLog {
/**
* 操作描述
*/
private String description;
/**
* 操作用戶
*/
private String username;
/**
* 操作時(shí)間
*/
private Long startTime;
/**
* 消耗時(shí)間
*/
private Integer spendTime;
/**
* 根路徑
*/
private String basePath;
/**
* URI
*/
private String uri;
/**
* URL
*/
private String url;
/**
* 請(qǐng)求類型
*/
private String method;
/**
* IP地址
*/
private String ip;
/**
* 請(qǐng)求參數(shù)
*/
private Object parameter;
/**
* 返回結(jié)果
*/
private Object result;
}自定義API異常:
public class ApiException extends RuntimeException {
private IErrorCode errorCode;
public ApiException(IErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public ApiException(String message) {
super(message);
}
public ApiException(Throwable cause) {
super(cause);
}
public ApiException(String message, Throwable cause) {
super(message, cause);
}
public IErrorCode getErrorCode() {
return errorCode;
}
}斷言處理類,用于拋出各種API異常:
public class Asserts {
public static void fail(String message) {
throw new ApiException(message);
}
public static void fail(IErrorCode errorCode) {
throw new ApiException(errorCode);
}
}全局異常處理:
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(value = ApiException.class)
public CommonResult handle(ApiException e) {
if (e.getErrorCode() != null) {
return CommonResult.failed(e.getErrorCode());
}
return CommonResult.failed(e.getMessage());
}
@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public CommonResult handleValidException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
String message = null;
if (bindingResult.hasErrors()) {
FieldError fieldError = bindingResult.getFieldError();
if (fieldError != null) {
message = fieldError.getField()+fieldError.getDefaultMessage();
}
}
return CommonResult.validateFailed(message);
}
@ResponseBody
@ExceptionHandler(value = BindException.class)
public CommonResult handleValidException(BindException e) {
BindingResult bindingResult = e.getBindingResult();
String message = null;
if (bindingResult.hasErrors()) {
FieldError fieldError = bindingResult.getFieldError();
if (fieldError != null) {
message = fieldError.getField()+fieldError.getDefaultMessage();
}
}
return CommonResult.validateFailed(message);
}
}統(tǒng)一日志處理切面:
@Aspect
@Component
@Order(1)
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * com.*.*.controller.*.*(..))||execution(public * com.*.*.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
}
@AfterReturning(value = "webLog()", returning = "ret")
public void doAfterReturning(Object ret) throws Throwable {
}
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//獲取當(dāng)前請(qǐng)求對(duì)象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//記錄請(qǐng)求信息(通過Logstash傳入Elasticsearch)
WebLog webLog = new WebLog();
Object result = joinPoint.proceed();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation log = method.getAnnotation(ApiOperation.class);
webLog.setDescription(log.value());
}
long endTime = System.currentTimeMillis();
String urlStr = request.getRequestURL().toString();
webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
webLog.setIp(request.getRemoteUser());
webLog.setMethod(request.getMethod());
webLog.setParameter(getParameter(method, joinPoint.getArgs()));
webLog.setResult(result);
webLog.setSpendTime((int) (endTime - startTime));
webLog.setStartTime(startTime);
webLog.setUri(request.getRequestURI());
webLog.setUrl(request.getRequestURL().toString());
Map<String,Object> logMap = new HashMap<>();
logMap.put("url",webLog.getUrl());
logMap.put("method",webLog.getMethod());
logMap.put("parameter",webLog.getParameter());
logMap.put("spendTime",webLog.getSpendTime());
logMap.put("description",webLog.getDescription());
// LOGGER.info("{}", JSONUtil.parse(webLog));
LOGGER.info(Markers.appendEntries(logMap), JSONUtil.parse(webLog).toString());
return result;
}
/**
* 根據(jù)方法和傳入的參數(shù)獲取請(qǐng)求參數(shù)
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
//將RequestBody注解修飾的參數(shù)作為請(qǐng)求參數(shù)
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
if (requestBody != null) {
argList.add(args[i]);
}
//將RequestParam注解修飾的參數(shù)作為請(qǐng)求參數(shù)
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
if (requestParam != null) {
Map<String, Object> map = new HashMap<>();
String key = parameters[i].getName();
if (!StringUtils.isEmpty(requestParam.value())) {
key = requestParam.value();
}
map.put(key, args[i]);
argList.add(map);
}
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}redis操作Service:
public interface RedisService {
/**
* 保存屬性
*/
void set(String key, Object value, long time);
/**
* 保存屬性
*/
void set(String key, Object value);
/**
* 獲取屬性
*/
Object get(String key);
/**
* 刪除屬性
*/
Boolean del(String key);
/**
* 批量刪除屬性
*/
Long del(List<String> keys);
/**
* 設(shè)置過期時(shí)間
*/
Boolean expire(String key, long time);
/**
* 獲取過期時(shí)間
*/
Long getExpire(String key);
/**
* 判斷是否有該屬性
*/
Boolean hasKey(String key);
/**
* 按delta遞增
*/
Long incr(String key, long delta);
/**
* 按delta遞減
*/
Long decr(String key, long delta);
/**
* 獲取Hash結(jié)構(gòu)中的屬性
*/
Object hGet(String key, String hashKey);
/**
* 向Hash結(jié)構(gòu)中放入一個(gè)屬性
*/
Boolean hSet(String key, String hashKey, Object value, long time);
/**
* 向Hash結(jié)構(gòu)中放入一個(gè)屬性
*/
void hSet(String key, String hashKey, Object value);
/**
* 直接獲取整個(gè)Hash結(jié)構(gòu)
*/
Map<Object, Object> hGetAll(String key);
/**
* 直接設(shè)置整個(gè)Hash結(jié)構(gòu)
*/
Boolean hSetAll(String key, Map<String, Object> map, long time);
/**
* 直接設(shè)置整個(gè)Hash結(jié)構(gòu)
*/
void hSetAll(String key, Map<String, ?> map);
/**
* 刪除Hash結(jié)構(gòu)中的屬性
*/
void hDel(String key, Object... hashKey);
/**
* 判斷Hash結(jié)構(gòu)中是否有該屬性
*/
Boolean hHasKey(String key, String hashKey);
/**
* Hash結(jié)構(gòu)中屬性遞增
*/
Long hIncr(String key, String hashKey, Long delta);
/**
* Hash結(jié)構(gòu)中屬性遞減
*/
Long hDecr(String key, String hashKey, Long delta);
/**
* 獲取Set結(jié)構(gòu)
*/
Set<Object> sMembers(String key);
/**
* 向Set結(jié)構(gòu)中添加屬性
*/
Long sAdd(String key, Object... values);
/**
* 向Set結(jié)構(gòu)中添加屬性
*/
Long sAdd(String key, long time, Object... values);
/**
* 是否為Set中的屬性
*/
Boolean sIsMember(String key, Object value);
/**
* 獲取Set結(jié)構(gòu)的長度
*/
Long sSize(String key);
/**
* 刪除Set結(jié)構(gòu)中的屬性
*/
Long sRemove(String key, Object... values);
/**
* 獲取List結(jié)構(gòu)中的屬性
*/
List<Object> lRange(String key, long start, long end);
/**
* 獲取List結(jié)構(gòu)的長度
*/
Long lSize(String key);
/**
* 根據(jù)索引獲取List中的屬性
*/
Object lIndex(String key, long index);
/**
* 向List結(jié)構(gòu)中添加屬性
*/
Long lPush(String key, Object value);
/**
* 向List結(jié)構(gòu)中添加屬性
*/
Long lPush(String key, Object value, long time);
/**
* 向List結(jié)構(gòu)中批量添加屬性
*/
Long lPushAll(String key, Object... values);
/**
* 向List結(jié)構(gòu)中批量添加屬性
*/
Long lPushAll(String key, Long time, Object... values);
/**
* 從List結(jié)構(gòu)中移除屬性
*/
Long lRemove(String key, long count, Object value);
}redis操作實(shí)現(xiàn)類:
public class RedisServiceImpl implements RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void set(String key, Object value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}
@Override
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
@Override
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public Boolean del(String key) {
return redisTemplate.delete(key);
}
@Override
public Long del(List<String> keys) {
return redisTemplate.delete(keys);
}
@Override
public Boolean expire(String key, long time) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
@Override
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
@Override
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
@Override
public Long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
@Override
public Long decr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, -delta);
}
@Override
public Object hGet(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
@Override
public Boolean hSet(String key, String hashKey, Object value, long time) {
redisTemplate.opsForHash().put(key, hashKey, value);
return expire(key, time);
}
@Override
public void hSet(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
@Override
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
@Override
public Boolean hSetAll(String key, Map<String, Object> map, long time) {
redisTemplate.opsForHash().putAll(key, map);
return expire(key, time);
}
@Override
public void hSetAll(String key, Map<String, ?> map) {
redisTemplate.opsForHash().putAll(key, map);
}
@Override
public void hDel(String key, Object... hashKey) {
redisTemplate.opsForHash().delete(key, hashKey);
}
@Override
public Boolean hHasKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
@Override
public Long hIncr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, delta);
}
@Override
public Long hDecr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, -delta);
}
@Override
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
@Override
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
@Override
public Long sAdd(String key, long time, Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
expire(key, time);
return count;
}
@Override
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
@Override
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
@Override
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
@Override
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
@Override
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}
@Override
public Object lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
@Override
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
@Override
public Long lPush(String key, Object value, long time) {
Long index = redisTemplate.opsForList().rightPush(key, value);
expire(key, time);
return index;
}
@Override
public Long lPushAll(String key, Object... values) {
return redisTemplate.opsForList().rightPushAll(key, values);
}
@Override
public Long lPushAll(String key, Long time, Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
expire(key, time);
return count;
}
@Override
public Long lRemove(String key, long count, Object value) {
return redisTemplate.opsForList().remove(key, count, value);
}
}logback-spring.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<!--引用默認(rèn)日志配置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!--使用默認(rèn)的控制臺(tái)日志輸出實(shí)現(xiàn)-->
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!--應(yīng)用名稱-->
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="springBoot"/>
<!--日志文件保存路徑-->
<property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
<!--LogStash訪問host-->
<springProperty name="LOG_STASH_HOST" scope="context" source="logstash.host" defaultValue="localhost"/>
<!--DEBUG日志輸出到文件-->
<appender name="FILE_DEBUG"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--輸出DEBUG以上級(jí)別日志-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<!--設(shè)置為默認(rèn)的文件日志格式-->
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--設(shè)置文件命名格式-->
<fileNamePattern>${LOG_FILE_PATH}/debug/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--設(shè)置日志文件大小,超過就重新生成文件,默認(rèn)10M-->
<maxFileSize>${LOG_FILE_MAX_SIZE:-10MB}</maxFileSize>
<!--日志文件保留天數(shù),默認(rèn)30天-->
<maxHistory>${LOG_FILE_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>
<!--ERROR日志輸出到文件-->
<appender name="FILE_ERROR"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--只輸出ERROR級(jí)別的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<!--設(shè)置為默認(rèn)的文件日志格式-->
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--設(shè)置文件命名格式-->
<fileNamePattern>${LOG_FILE_PATH}/error/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--設(shè)置日志文件大小,超過就重新生成文件,默認(rèn)10M-->
<maxFileSize>${LOG_FILE_MAX_SIZE:-10MB}</maxFileSize>
<!--日志文件保留天數(shù),默認(rèn)30天-->
<maxHistory>${LOG_FILE_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>
<!--DEBUG日志輸出到LogStash-->
<appender name="LOG_STASH_DEBUG" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<destination>${LOG_STASH_HOST}:4560</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定義日志輸出格式-->
<pattern>
<pattern>
{
"project": "mall-swarm",
"level": "%level",
"service": "${APP_NAME:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger",
"message": "%message",
"stack_trace": "%exception{20}"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--當(dāng)有多個(gè)LogStash服務(wù)時(shí),設(shè)置訪問策略為輪詢-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>
<!--ERROR日志輸出到LogStash-->
<appender name="LOG_STASH_ERROR" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<destination>${LOG_STASH_HOST}:4561</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定義日志輸出格式-->
<pattern>
<pattern>
{
"project": "mall-swarm",
"level": "%level",
"service": "${APP_NAME:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger",
"message": "%message",
"stack_trace": "%exception{20}"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--當(dāng)有多個(gè)LogStash服務(wù)時(shí),設(shè)置訪問策略為輪詢-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>
<!--業(yè)務(wù)日志輸出到LogStash-->
<appender name="LOG_STASH_BUSINESS" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${LOG_STASH_HOST}:4562</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定義日志輸出格式-->
<pattern>
<pattern>
{
"project": "mall-swarm",
"level": "%level",
"service": "${APP_NAME:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger",
"message": "%message",
"stack_trace": "%exception{20}"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--當(dāng)有多個(gè)LogStash服務(wù)時(shí),設(shè)置訪問策略為輪詢-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>
<!--接口訪問記錄日志輸出到LogStash-->
<appender name="LOG_STASH_RECORD" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${LOG_STASH_HOST}:4563</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定義日志輸出格式-->
<pattern>
<pattern>
{
"project": "mall-swarm",
"level": "%level",
"service": "${APP_NAME:-}",
"class": "%logger",
"message": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--當(dāng)有多個(gè)LogStash服務(wù)時(shí),設(shè)置訪問策略為輪詢-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>
<!--控制框架輸出日志-->
<logger name="org.slf4j" level="INFO"/>
<logger name="springfox" level="INFO"/>
<logger name="io.swagger" level="INFO"/>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate.validator" level="INFO"/>
<logger name="com.alibaba.nacos.client.naming" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_DEBUG"/>
<appender-ref ref="FILE_ERROR"/>
<appender-ref ref="LOG_STASH_DEBUG"/>
<appender-ref ref="LOG_STASH_ERROR"/>
</root>
<logger name="com.*.*.common.log.WebLogAspect" level="DEBUG">
<appender-ref ref="LOG_STASH_RECORD"/>
</logger>
<logger name="com.*.*" level="DEBUG">
<appender-ref ref="LOG_STASH_BUSINESS"/>
</logger>
</configuration>5.admin代碼部分
Swagger API文檔相關(guān)配置:
@Configuration
@EnableSwagger2
public class SwaggerConfig extends BaseSwaggerConfig {
@Override
public SwaggerProperties swaggerProperties() {
return SwaggerProperties.builder()
.apiBasePackage("com.admin.controller")
.title("后臺(tái)系統(tǒng)")
.description("后臺(tái)相關(guān)接口文檔")
.contactName("admin")
.version("1.0")
.enableSecurity(true)
.build();
}
@Bean
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return generateBeanPostProcessor();
}
}后臺(tái)用戶管理:
@Controller
@Api(tags = "UmsAdminController", description = "后臺(tái)用戶管理")
@RequestMapping("/admin")
public class UmsAdminController {
@Autowired
private UmsAdminService adminService;
@ApiOperation(value = "用戶注冊(cè)")
@RequestMapping(value = "/register", method = RequestMethod.POST)
@ResponseBody
public CommonResult<UmsAdmin> register(@Validated @RequestBody UmsAdminParam umsAdminParam) {
UmsAdmin umsAdmin = adminService.register(umsAdminParam);
if (umsAdmin == null) {
return CommonResult.failed();
}
return CommonResult.success(umsAdmin);
}
@ApiOperation(value = "登錄以后返回token")
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public CommonResult login(@Validated @RequestBody UmsAdminLoginParam umsAdminLoginParam) {
return adminService.login(umsAdminLoginParam.getUsername(),umsAdminLoginParam.getPassword());
}
@ApiOperation(value = "登出功能")
@RequestMapping(value = "/logout", method = RequestMethod.POST)
@ResponseBody
public CommonResult logout() {
return CommonResult.success(null);
}
@ApiOperation("根據(jù)用戶名獲取通用用戶信息")
@RequestMapping(value = "/loadByUsername", method = RequestMethod.GET)
@ResponseBody
public UserDto loadUserByUsername(@RequestParam String username) {
UserDto userDTO = adminService.loadUserByUsername(username);
return userDTO;
}
}UmsAdminService實(shí)現(xiàn)類:
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
private static final Logger LOGGER = LoggerFactory.getLogger(UmsAdminServiceImpl.class);
@Autowired
private UmsAdminMapper adminMapper;
@Autowired
private UmsAdminLoginLogMapper loginLogMapper;
@Autowired
private AuthService authService;
@Autowired
private HttpServletRequest request;
@Override
public UmsAdmin register(UmsAdminParam umsAdminParam) {
UmsAdmin umsAdmin = new UmsAdmin();
BeanUtils.copyProperties(umsAdminParam, umsAdmin);
umsAdmin.setCreateTime(new Date());
umsAdmin.setStatus(1);
//查詢是否有相同用戶名的用戶
List<UmsAdmin> umsAdminList = adminMapper.selectList(new QueryWrapper<UmsAdmin>().eq("username",umsAdmin.getUsername()));
if (umsAdminList.size() > 0) {
return null;
}
//將密碼進(jìn)行加密操作
String encodePassword = BCrypt.hashpw(umsAdmin.getPassword());
umsAdmin.setPassword(encodePassword);
adminMapper.insert(umsAdmin);
return umsAdmin;
}
@Override
public CommonResult login(String username, String password) {
if(StrUtil.isEmpty(username)||StrUtil.isEmpty(password)){
Asserts.fail("用戶名或密碼不能為空!");
}
Map<String, String> params = new HashMap<>();
params.put("client_id", AuthConstant.ADMIN_CLIENT_ID);
params.put("client_secret","123456");
params.put("grant_type","password");
params.put("username",username);
params.put("password",password);
CommonResult restResult = authService.getAccessToken(params);
if(ResultCode.SUCCESS.getCode()==restResult.getCode()&&restResult.getData()!=null){
}
return restResult;
}
@Override
public UserDto loadUserByUsername(String username) {
//獲取用戶信息
UmsAdmin admin = getAdminByUsername(username);
if (admin != null) {
UserDto userDTO = new UserDto();
BeanUtils.copyProperties(admin,userDTO);
return userDTO;
}
return null;
}
@Override
public UmsAdmin getAdminByUsername(String username) {
List<UmsAdmin> adminList = adminMapper.selectList(new QueryWrapper<UmsAdmin>().eq("username",username));
if (adminList != null && adminList.size() > 0) {
return adminList.get(0);
}
return null;
}
}認(rèn)證服務(wù)遠(yuǎn)程調(diào)用Service:
@FeignClient("oauth")
public interface AuthService {
@PostMapping(value = "/oauth/token")
CommonResult getAccessToken(@RequestParam Map<String, String> parameters);
}后臺(tái)管理員Service:
public interface UmsAdminService {
/**
* 注冊(cè)功能
*/
UmsAdmin register(UmsAdminParam umsAdminParam);
/**
* 登錄功能
* @param username 用戶名
* @param password 密碼
* @return 調(diào)用認(rèn)證中心返回結(jié)果
*/
CommonResult login(String username, String password);
/**
* 獲取用戶信息
*/
UserDto loadUserByUsername(String username);
/**
* 根據(jù)用戶名獲取后臺(tái)管理員
*/
UmsAdmin getAdminByUsername(String username);
}yml配置:
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
url: jdbc:mysql://localhost:3306/admin?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: root
druid:
initial-size: 5 #連接池初始化大小
min-idle: 10 #最小空閑連接數(shù)
max-active: 20 #最大連接數(shù)
web-stat-filter:
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不統(tǒng)計(jì)這些請(qǐng)求數(shù)據(jù)
stat-view-servlet: #訪問監(jiān)控網(wǎng)頁的登錄用戶名和密碼
login-username: druid
login-password: druid
redis:
host: localhost # Redis服務(wù)器地址
database: 0 # Redis數(shù)據(jù)庫索引(默認(rèn)為0)
port: 6379 # Redis服務(wù)器連接端口
password: # Redis服務(wù)器連接密碼(默認(rèn)為空)
timeout: 3000ms # 連接超時(shí)時(shí)間(毫秒)
management: #開啟SpringBoot Admin的監(jiān)控
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
redis:
database: admin
key:
admin: 'ums:admin'
expire:
common: 86400 # 24小時(shí)
feign:
okhttp:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl總結(jié)
本人第一次寫文章,如有看不懂的多多包涵
以上就是今天要講的內(nèi)容,本文僅僅簡單介紹了統(tǒng)一認(rèn)證的使用,后續(xù)會(huì)把源碼地址分享出來。
項(xiàng)目地址:https://gitee.com/zhouwudi/SpringSecurityOauth2
到此這篇關(guān)于Spring Cloud 完整整合 Security + Oauth2 + jwt實(shí)現(xiàn)權(quán)限認(rèn)證的文章就介紹到這了,更多相關(guān)Spring Cloud Security + Oauth2 + jwt權(quán)限認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java數(shù)據(jù)導(dǎo)出功能之導(dǎo)出Excel文件實(shí)例
這篇文章主要介紹了Java數(shù)據(jù)導(dǎo)出功能之導(dǎo)出Excel文件實(shí)例,本文給出了jar包的下載地址,并給出了導(dǎo)出Excel文件代碼實(shí)例,需要的朋友可以參考下2015-06-06
淺談SpringBoot之開啟數(shù)據(jù)庫遷移的FlyWay使用
這篇文章主要介紹了淺談SpringBoot之開啟數(shù)據(jù)庫遷移的FlyWay使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01
關(guān)于spring?boot使用?jdbc+mysql?連接的問題
這篇文章主要介紹了spring?boot使用?jdbc+mysql?連接,在這里mysql?8.x版本驅(qū)動(dòng)包,要使用?com.mysql.cj.jdbc.Driver作為驅(qū)動(dòng)類,文中給大家詳細(xì)介紹,需要的朋友可以參考下2022-03-03
如何通過ServletInputStream讀取http請(qǐng)求傳入的數(shù)據(jù)
這篇文章主要介紹了如何通過ServletInputStream讀取http請(qǐng)求傳入的數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
詳解Java8新特性Stream之list轉(zhuǎn)map及問題解決
這篇文章主要介紹了詳解Java8新特性Stream之list轉(zhuǎn)map及問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Spring?Boot實(shí)現(xiàn)web.xml功能示例詳解
這篇文章主要介紹了Spring?Boot實(shí)現(xiàn)web.xml功能,通過本文介紹我們了解到,在Spring Boot應(yīng)用中,我們可以通過注解和編程兩種方式實(shí)現(xiàn)web.xml的功能,包括如何創(chuàng)建及注冊(cè)Servlet、Filter以及Listener等,需要的朋友可以參考下2023-09-09

