升级Spring Cloud Hoxton & Alibaba,认证授权改用Oauth2。

This commit is contained in:
macro
2020-08-15 10:51:40 +08:00
parent d004888a10
commit dae82f62ed
258 changed files with 4671 additions and 3092 deletions

View File

@@ -0,0 +1,17 @@
package com.macro.mall.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = "com.macro.mall")
public class MallAuthApplication {
public static void main(String[] args) {
SpringApplication.run(MallAuthApplication.class, args);
}
}

View File

@@ -0,0 +1,29 @@
package com.macro.mall.auth.component;
import com.macro.mall.auth.domain.SecurityUser;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* JWT内容增强器
* Created by macro on 2020/6/19.
*/
@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设置到JWT中
info.put("id", securityUser.getId());
info.put("client_id",securityUser.getClientId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}

View File

@@ -0,0 +1,89 @@
package com.macro.mall.auth.config;
import com.macro.mall.auth.component.JwtTokenEnhancer;
import com.macro.mall.auth.service.impl.UserServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
/**
* 认证服务器配置
* Created by macro on 2020/6/19.
*/
@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)
.refreshTokenValiditySeconds(86400)
.and()
.withClient("portal-app")
.secret(passwordEncoder.encode("123456"))
.scopes("all")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(86400);
}
@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的内容增强器
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService) //配置加载用户信息的服务
.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下的证书中获取秘钥对
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
}

View File

@@ -0,0 +1,27 @@
package com.macro.mall.auth.config;
import com.macro.mall.common.config.BaseSwaggerConfig;
import com.macro.mall.common.domain.SwaggerProperties;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Swagger API文档相关配置
* Created by macro on 2018/4/26.
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig extends BaseSwaggerConfig {
@Override
public SwaggerProperties swaggerProperties() {
return SwaggerProperties.builder()
.apiBasePackage("com.macro.mall.auth.controller")
.title("mall认证中心")
.description("mall认证中心相关接口文档")
.contactName("macro")
.version("1.0")
.enableSecurity(true)
.build();
}
}

View File

@@ -0,0 +1,41 @@
package com.macro.mall.auth.config;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* SpringSecurity配置
* Created by macro on 2020/6/19.
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.antMatchers("/rsa/publicKey").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.anyRequest().authenticated();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,23 @@
package com.macro.mall.auth.constant;
/**
* 消息常量
* Created by macro on 2020/6/19.
*/
public class MessageConstant {
public static final String LOGIN_SUCCESS = "登录成功!";
public static final String USERNAME_PASSWORD_ERROR = "用户名或密码错误!";
public static final String CREDENTIALS_EXPIRED = "该账户的登录凭证已过期,请重新登录!";
public static final String ACCOUNT_DISABLED = "该账户已被禁用,请联系管理员!";
public static final String ACCOUNT_LOCKED = "该账号已被锁定,请联系管理员!";
public static final String ACCOUNT_EXPIRED = "该账号已过期,请联系管理员!";
public static final String PERMISSION_DENIED = "没有访问权限,请联系管理员!";
}

View File

@@ -0,0 +1,55 @@
package com.macro.mall.auth.controller;
import com.macro.mall.auth.domain.Oauth2TokenDto;
import com.macro.mall.common.api.CommonResult;
import com.macro.mall.common.constant.AuthConstant;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import java.security.Principal;
import java.util.Map;
/**
* 自定义Oauth2获取令牌接口
* Created by macro on 2020/7/17.
*/
@RestController
@Api(tags = "AuthController", description = "认证中心登录认证")
@RequestMapping("/oauth")
public class AuthController {
@Autowired
private TokenEndpoint tokenEndpoint;
@ApiOperation("Oauth2获取token")
@ApiImplicitParams({
@ApiImplicitParam(name = "grant_type", value = "授权模式", required = true),
@ApiImplicitParam(name = "client_id", value = "Oauth2客户端ID", required = true),
@ApiImplicitParam(name = "client_secret", value = "Oauth2客户端秘钥", required = true),
@ApiImplicitParam(name = "refresh_token", value = "刷新token"),
@ApiImplicitParam(name = "username", value = "登录用户名"),
@ApiImplicitParam(name = "password", value = "登录密码")
})
@RequestMapping(value = "/token", method = RequestMethod.POST)
public CommonResult<Oauth2TokenDto> postAccessToken(@ApiIgnore Principal principal, @ApiIgnore @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, 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);
}
}

View File

@@ -0,0 +1,30 @@
package com.macro.mall.auth.controller;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
/**
* 获取RSA公钥接口
* Created by macro on 2020/6/19.
*/
@RestController
public class KeyPairController {
@Autowired
private KeyPair keyPair;
@GetMapping("/rsa/publicKey")
public Map<String, Object> getKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}

View File

@@ -0,0 +1,24 @@
package com.macro.mall.auth.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Oauth2获取Token返回信息封装
* Created by macro on 2020/7/17.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class Oauth2TokenDto {
@ApiModelProperty("访问令牌")
private String token;
@ApiModelProperty("刷令牌")
private String refreshToken;
@ApiModelProperty("访问令牌头前缀")
private String tokenHead;
@ApiModelProperty("有效时间(秒)")
private int expiresIn;
}

View File

@@ -0,0 +1,95 @@
package com.macro.mall.auth.domain;
import com.macro.mall.common.domain.UserDto;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
/**
* 登录用户信息
* Created by macro on 2020/6/19.
*/
@Data
public class SecurityUser implements UserDetails {
/**
* ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 用户状态
*/
private Boolean enabled;
/**
* 登录客户端ID
*/
private String clientId;
/**
* 权限数据
*/
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;
}
}

View File

@@ -0,0 +1,20 @@
package com.macro.mall.auth.exception;
import com.macro.mall.common.api.CommonResult;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 全局处理Oauth2抛出的异常
* Created by macro on 2020/7/17.
*/
@ControllerAdvice
public class Oauth2ExceptionHandler {
@ResponseBody
@ExceptionHandler(value = OAuth2Exception.class)
public CommonResult handleOauth2(OAuth2Exception e) {
return CommonResult.failed(e.getMessage());
}
}

View File

@@ -0,0 +1,16 @@
package com.macro.mall.auth.service;
import com.macro.mall.common.domain.UserDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Created by macro on 2019/10/18.
*/
@FeignClient("mall-admin")
public interface UmsAdminService {
@GetMapping("/admin/loadByUsername")
UserDto loadUserByUsername(@RequestParam String username);
}

View File

@@ -0,0 +1,15 @@
package com.macro.mall.auth.service;
import com.macro.mall.common.domain.UserDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Created by macro on 2020/7/16.
*/
@FeignClient("mall-portal")
public interface UmsMemberService {
@GetMapping("/sso/loadByUsername")
UserDto loadUserByUsername(@RequestParam String username);
}

View File

@@ -0,0 +1,61 @@
package com.macro.mall.auth.service.impl;
import com.macro.mall.auth.domain.SecurityUser;
import com.macro.mall.auth.constant.MessageConstant;
import com.macro.mall.auth.service.UmsAdminService;
import com.macro.mall.auth.service.UmsMemberService;
import com.macro.mall.common.constant.AuthConstant;
import com.macro.mall.common.domain.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
/**
* 用户管理业务类
* Created by macro on 2020/6/19.
*/
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private UmsAdminService adminService;
@Autowired
private UmsMemberService memberService;
@Autowired
private HttpServletRequest request;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String clientId = request.getParameter("client_id");
UserDto userDto;
if(AuthConstant.ADMIN_CLIENT_ID.equals(clientId)){
userDto = adminService.loadUserByUsername(username);
}else{
userDto = memberService.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;
}
}

View File

@@ -0,0 +1,13 @@
server:
port: 8401
management:
endpoints:
web:
exposure:
include: "*"
feign:
okhttp:
enabled: true
ribbon:
ConnectTimeout: 3000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)

View File

@@ -0,0 +1,11 @@
spring:
cloud:
nacos:
discovery:
server-addr: http://localhost:8848
config:
server-addr: http://localhost:8848
file-extension: yaml
logging:
level:
root: debug

View File

@@ -0,0 +1,11 @@
spring:
cloud:
nacos:
discovery:
server-addr: http://nacos-registry:8848
config:
server-addr: http://nacos-registry:8848
file-extension: yaml
logging:
level:
root: info

View File

@@ -0,0 +1,5 @@
spring:
profiles:
active: dev
application:
name: mall-auth

Binary file not shown.