1. Overview of single sign-on
(1) What is single sign-on: each subsystem searches from a third-party authentication system instead of each system passing its own session verification .
(3) Single sign-on implementation framework:
apache Shiro
CAS
springsecurity
Two. Oauth2 authentication WeChat authentication third-party login
1. Authorization code mode: the resource owner (user) initiates WeChat login, the platform calls the WeChat interface, arrives at the authorization page, and the mobile phone wx scan code login user agrees to authorize, and WeChat will Verify the user and give the platform an authorization code after passing. The platform takes the authorization code to the WeChat authentication service to apply for a token, and returns the token to the platform. The platform then carries the token to adjust the WeChat user information service. The WeChat user information service verifies the validity of the token and responds to the platform.
The platform displays the WeChat avatar.
The client (platform) gets the authorization code and goes to the authentication service to apply for a token and authorize The service generates a token through private key encryption,
The token is given to the client, and the token is carried to access the resource service
The course service is equivalent to the resource storage public key (according to the public key verification order The legitimacy of the card===The public key unlocks the token and releases it; if it cannot be unlocked, the service is denied)
Course service is equivalent to resource service
Release swagger
"/swagger-resources ","/swagger-resources/configuration/security", "/swagger-ui.html","/webjars/**","/course/coursepic/list/*").permitAll( )
mport org.springframework.context.annotation.Bean ;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration .EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders .HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web .configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web .configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore ;
import org.springframework.security.oauth2.provider.token.store .JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store .JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true )//PreAuthorize annotation on activation method
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//Public key
private static final String PUBLIC_KEY = "publickey.txt";
//Define JwtTokenStore, use jwt token
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
//Define JJwtAccessTokenConverter, use jwt token
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* Obtain asymmetric encryption public key Key
* @return Public Key Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining(" "));
} catch (IOException ioe) {
return null;
}
}
//Http security configuration, for each http request to the system Link for verification
@Override
public void configure(HttpSecurity http) throws Exception {
//All requests must be authenticated
http.authorizeRequests()
.antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources","/swagger-resources/configuration/security",
"/swagger-ui.html","/webjars/**","/course/coursepic/list/*").permitAll ()
.anyRequest().authenticated();
}
}
Access to course services only: head KEY: authorization
value: Bear+accesstoken< /p>
Can access only
2. Password algorithm mode
No need to apply for authorization code, username and password are all you need;
< Strong>jwt contains user information, which can prevent the resource service from being unable to solve the verification and also requesting the authentication service to verify. High efficiency
It is a json string that is easy to parse, easy to expand, asymmetric and safe, but long
is divided into header (encryption algorithm), payload (added by yourself), signature (the only one issued by the authentication server to prevent theft).
(1) The controller of the authentication service
How to apply for a token
em>
clientid, clientpassword
bodyheader is encapsulated in HTTPentity
getHttpBasic< /pre>MultiValueMapbody = new LinkedMultiValueMap<>();
body.add("grant_type","password");
body.add("username","itcast");
body.add("password","123");
MultiValueMapheaders = new LinkedMultiValueMap<>() ;
headers.add("Authorization",this.getHttpBasic("XcWebApp","XcWebApp"));
HttpEntity> requestEntity = new HttpEntity<>(body,headers); private String getHttpBasic(String clientId, String clientPassword) {
String value =clientId+":"+clientPassword ;
byte[] encode = Base64Utils .encode(value.getBytes());
return "Basic "+new String(encode);
}uri
//http://localhost :40400/auth/oauth/token
ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
// http://localhost< /span>:40400
URI uri = serviceInstance.getUri();
String url = uri+"/auth/oauth/token";ResponseEntity1 Override
2 @PostMapping("/userlogin")
3 public LoginResult login(LoginRequest loginRequest) {
4
5 //Judgment parameters
6 if (StringUtils.isEmpty(loginRequest.getUsername())){
7 ExceptionCast.cast(AuthCode.AUTH_USERNAME_NONE);
8 }
9 if (StringUtils.isEmpty(loginRequest.getPassword())){
10 ExceptionCast.cast(AuthCode.AUTH_PASSWORD_NONE);
11 }
12
13 //Apply for token
14 AuthToken authToken = authService.login(loginRequest.getUsername(),loginRequest.getPassword( ),clientId,clientSecret);
15
16 String jti = authToken.getJti();
17
18 //Save token information in cookie
19 this .saveTokenToCookie(jti);
20
21 return new LoginResult(CommonCode.SUCCESS,jti);
22 }
23
24 private void saveTokenToCookie(String jti) {
25 HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getResponse();
26
27 //httpOnly:false. Allow browser to get cookies
28 CookieUtil.addCookie(response,cookieDomain,"/","uid",jti,cookieMaxAge,false);
29 }Test< p>
Actual results< p>
(2) authentication service service
public AuthToken login(String username, String password, String clientId, String clientSecret) {
//Apply for token
AuthToken authToken = this.applyToken(username,password,clientId,clientSecret);
//Save the token to redis
boolean result = this.saveTokenToRedis(authToken);
if (!result){
//Failed to deposit
ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
}
return authToken;
}
//Save to redis
private boolean saveTokenToRedis(AuthToken authToken) {
String key = "user_token:"+authToken.getJti();
String tokenString = JSON.toJSONString(authToken);
stringRedisTemplate.boundValueOps(key).set(tokenString,tokenValiditySeconds, TimeUnit.SECONDS);
Long expire = stringRedisTemplate.getExpire(key);
return expire>0;
}
//Apply for token
private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
URI uri = serviceInstance.getUri();
String url = uri+"/auth/oauth/token";
MultiValueMapbody = new LinkedMultiValueMap<>() ;
body.add("grant_type","password");
body.add("username",username);
body.add("password",password);
MultiValueMapheaders = new LinkedMultiValueMap<>() ;
headers.add("Authorization",this.getHttpBasic(clientId, clientSecret));
HttpEntity> requestEntity = new HttpEntity<>(body,headers);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401){
super.handleError(response);
}
}
});
ResponseEntityThree, specific single sign-on to achieve authentication service
(1) Configuration file
p>
1
AuthorizationServerConfigKey asymmetric encryption, private key encryption, public key decryption< span style="color: #0000ff;">package com.xuecheng.auth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.client.JdbcClientDetailsService ;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter ;
import org.springframework.security.oauth2.provider.token.TokenStore ;
import org.springframework.security.oauth2.provider.token.store .JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store .JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store .KeyStoreKeyFactory;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
//jwt token converter
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
UserDetailsService userDetailsService;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
TokenStore tokenStore;
@Autowired
private CustomUserAuthenticationConverter customUserAuthenticationConverter;
//Read the configuration of the key
@Bean("keyProp")
public KeyProperties keyProperties(){
return new KeyProperties();
}
@Resource(name = "keyProp")
private KeyProperties keyProperties;
//Client configuration
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(this.dataSource).clients(this span>.clientDetails());
/* clients.inMemory()
.withClient("XcWebApp")//Client id
.secret("XcWebApp")//Password, keep it secret
.accessTokenValiditySeconds(60)//Access token validity period
.refreshTokenValiditySeconds(60)//Refresh token validity period
//Authorization client request authentication service type authorization_code: generate token according to authorization code,
// client_credentials: client authentication, refresh_token: refresh token, password: password authentication
.authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token", "password")
.scopes("app");//Client scope, custom name, required*/
}
//Token storage method
// @Bean
// public InMemoryTokenStore tokenStore() {
// //Store token to memory
// return new InMemoryTokenStore();
//}
// @Bean
// public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory){
// RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
// return redisTokenStore;
// }
@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory
(keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray())
.getKeyPair(keyProperties.getKeyStore().getAlias(),keyProperties.getKeyStore().getPassword().toCharArray());
converter.setKeyPair(keyPair);
//Configure a custom CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
//授权服务器端点配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/*CollectiontokenEnhancers = applicationContext.getBeansOfType(TokenEnhancer.class).values(); */
TokenEnhancerChain tokenEnhancerChain=new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(new ArrayList<>(tokenEnhancers));
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setReuseRefreshToken(true);
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenStore(tokenStore);
defaultTokenServices.setAccessTokenValiditySeconds(1111111);
defaultTokenServices.setRefreshTokenValiditySeconds(1111111);
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
//.tokenStore(tokenStore);
.tokenServices(defaultTokenServices);
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.authenticationManager(authenticationManager)//认证管理器
.tokenStore(tokenStore)//令牌存储
.userDetailsService(userDetailsService);//用户信息service
}
//授权服务器的安全配置
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
// oauthServer.checkTokenAccess("isAuthenticated()");//校验token需要认证通过,可采用http basic认证
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
2CustomUserAuthenticationConverter生成令牌信息package com.xuecheng.auth.config;
import com.xuecheng.auth.service.UserJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Autowired
UserDetailsService userDetailsService;
@Override
public MapconvertUserAuthentication(Authentication authentication) {
LinkedHashMap response = new LinkedHashMap();
String name = authentication.getName();
response.put("user_name", name);
Object principal = authentication.getPrincipal();
UserJwt userJwt = null;
if(principal instanceof UserJwt){
userJwt = (UserJwt) principal;
}else{
//refresh_token默认不去调用userdetailService获取用户信息,这里我们手动去调用,得到 UserJwt
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
userJwt = (UserJwt) userDetails;
}
response.put("name", userJwt.getName());
response.put("id", userJwt.getId());
response.put("utype",userJwt.getUtype());
response.put("userpic",userJwt.getUserpic());
response.put("companyId",userJwt.getCompanyId());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}
3WebSecurityConfig
对登录放行
密码加盐加密1 import org.springframework.context.annotation.Bean;
2 import org.springframework.context.annotation.Configuration;
3 import org.springframework.core.annotation.Order;
4 import org.springframework.security.authentication.AuthenticationManager;
5 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6 import org.springframework.security.config.annotation.web.builders.WebSecurity;
7 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
8 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
9 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
10 import org.springframework.security.crypto.password.PasswordEncoder;
11
12 @Configuration
13 @EnableWebSecurity
14 @Order(-1)
15 class WebSecurityConfig extends WebSecurityConfigurerAdapter {
16
17 @Override
18 public void configure(WebSecurity web) throws Exception {
19 web.ignoring().antMatchers("/userlogin","/userlogout","/userjwt");
20
21 }
22 @Bean
23 @Override
24 public AuthenticationManager authenticationManagerBean() throws Exception {
25 AuthenticationManager manager = super.authenticationManagerBean();
26 return manager;
27 }
28 //采用bcrypt对密码进行编码
29 @Bean
30 public PasswordEncoder passwordEncoder() {
31 return new BCryptPasswordEncoder();
32 }
33
34 @Override
35 public void configure(HttpSecurity http) throws Exception {
36 http.csrf().disable()
37 .httpBasic().and()
38 .formLogin()
39 .and()
40 .authorizeRequests().anyRequest().authenticated();
41
42 }
43 }authservice写认证
package com.xuecheng.auth.service;
import com.alibaba.fastjson.JSON;
import com.xuecheng.filesystem.framework.client.XcServiceList;
import com.xuecheng.filesystem.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.filesystem.framework.domain.ucenter.response.AuthCode;
import com.xuecheng.filesystem.framework.exception.ExceptionCast;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class AuthService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${auth.tokenValiditySeconds}")
private long tokenValiditySeconds;
/**
* 用户认证登录,申请令牌
* @param username
* @param password
* @param clientId
* @param clientSecret
* @return
*/
public AuthToken login(String username, String password, String clientId, String clientSecret) {
//申请令牌
AuthToken authToken = this.applyToken(username,password,clientId,clientSecret);
//将令牌存入redis
boolean result = this.saveTokenToRedis(authToken);
if (!result){
//存入失败
ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
}
return authToken;
}
//存入redis
private boolean saveTokenToRedis(AuthToken authToken) {
String key = "user_token:"+authToken.getJti();
String tokenString = JSON.toJSONString(authToken);
stringRedisTemplate.boundValueOps(key).set(tokenString,tokenValiditySeconds, TimeUnit.SECONDS);
Long expire = stringRedisTemplate.getExpire(key);
return expire>0;
}
//申请令牌
private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
URI uri = serviceInstance.getUri();
String url = uri+"/auth/oauth/token";
MultiValueMapbody = new LinkedMultiValueMap<>();
body.add("grant_type","password");
body.add("username",username);
body.add("password",password);
MultiValueMapheaders = new LinkedMultiValueMap<>();
headers.add("Authorization",this.getHttpBasic(clientId,clientSecret));
HttpEntity> requestEntity = new HttpEntity<>(body,headers);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401){
super.handleError(response);
}
}
});
ResponseEntity
userjwt用户信息融到令牌中
import lombok.Data;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
@Data
@ToString
public class UserJwt extends User {
private String id;
private String name;
private String userpic;
private String utype;
private String companyId;
public UserJwt(String username, String password, Collection extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
}
yml配置
1访问路径,redis信息
2auth设置redis过期时间==session过期时间(过期重新登录用)
3私钥位置
4cookie域名(适配到不同域中)和maxage
server:
port: ${PORT:40400}
servlet:
context-path: /auth
spring:
application:
name: xc-service-ucenter-auth
redis:
host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:6379}
timeout: 5000 #连接超时 毫秒
jedis:
pool:
maxActive: 3
maxIdle: 3
minIdle: 1
maxWait: -1 #连接池最大等行时间 -1没有限制
datasource:
druid:
url: ${MYSQL_URL:jdbc:mysql://localhost:3306/xc_user?characterEncoding=utf-8}
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
initialSize: 5 #初始建立连接数量
minIdle: 5 #最小连接数量
maxActive: 20 #最大连接数量
maxWait: 10000 #获取连接最大等待时间,毫秒
testOnBorrow: true #申请连接时检测连接是否有效
testOnReturn: false #归还连接时检测连接是否有效
timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒)
auth:
tokenValiditySeconds: 1200 #token存储到redis的过期时间
clientId: XcWebApp
clientSecret: XcWebApp
cookieDomain: xuecheng.com
cookieMaxAge: -1
encrypt:
key-store:
location: classpath:/xc.keystore
secret: xuechengkeystore
alias: xckey
password: xuecheng
eureka:
client:
registerWithEureka: true #服务注册开关
fetchRegistry: true #服务发现开关
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: ${IP_ADDRESS:127.0.0.1}
instance-id: ${spring.application.name}:${server.port} #指定实例id
ribbon:
MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器
MaxAutoRetriesNextServer: 3 #切换实例的重试次数
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 6000 #请求处理的超时时间
(二)单点登录的特点是:
1、认证系统为独立的系统。
2、各子系统通过Http或其它协议与认证系统通信,完成用户认证。
3、用户身份信息存储在Redis集群。
mport org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "publickey.txt";
//定义JwtTokenStore,使用jwt令牌
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
//定义JJwtAccessTokenConverter,使用jwt令牌
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining(" "));
} catch (IOException ioe) {
return null;
}
}
//Http安全配置,对每个到达系统的http请求链接进行校验
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
.antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
"/swagger-resources","/swagger-resources/configuration/security",
"/swagger-ui.html","/webjars/**","/course/coursepic/list/*").permitAll()
.anyRequest().authenticated();
}
}MultiValueMapbody = new LinkedMultiValueMap<>();
body.add("grant_type","password");
body.add("username","itcast");
body.add("password","123");
MultiValueMapheaders = new LinkedMultiValueMap<>();
headers.add("Authorization",this.getHttpBasic("XcWebApp","XcWebApp"));
HttpEntity> requestEntity = new HttpEntity<>(body,headers); private String getHttpBasic(String clientId, String clientPassword) {
String value =clientId+":"+clientPassword ;
byte[] encode = Base64Utils.encode(value.getBytes());
return "Basic "+new String(encode);
}//http://localhost:40400/auth/oauth/token
ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
// http://localhost:40400
URI uri = serviceInstance.getUri();
String url = uri+"/auth/oauth/token";1 Override
2 @PostMapping("/userlogin")
3 public LoginResult login(LoginRequest loginRequest) {
4
5 //判断参数
6 if (StringUtils.isEmpty(loginRequest.getUsername())){
7 ExceptionCast.cast(AuthCode.AUTH_USERNAME_NONE);
8 }
9 if (StringUtils.isEmpty(loginRequest.getPassword())){
10 ExceptionCast.cast(AuthCode.AUTH_PASSWORD_NONE);
11 }
12
13 //申请令牌
14 AuthToken authToken = authService.login(loginRequest.getUsername(),loginRequest.getPassword(),clientId,clientSecret);
15
16 String jti = authToken.getJti();
17
18 //将令牌信息存入cookie
19 this.saveTokenToCookie(jti);
20
21 return new LoginResult(CommonCode.SUCCESS,jti);
22 }
23
24 private void saveTokenToCookie(String jti) {
25 HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
26
27 //httpOnly:false。 允许浏览器获取cookie
28 CookieUtil.addCookie(response,cookieDomain,"/","uid",jti,cookieMaxAge,false);
29 }public AuthToken login(String username, String password, String clientId, String clientSecret) {
//申请令牌
AuthToken authToken = this.applyToken(username,password,clientId,clientSecret);
//将令牌存入redis
boolean result = this.saveTokenToRedis(authToken);
if (!result){
//存入失败
ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
}
return authToken;
}
//存入redis
private boolean saveTokenToRedis(AuthToken authToken) {
String key = "user_token:"+authToken.getJti();
String tokenString = JSON.toJSONString(authToken);
stringRedisTemplate.boundValueOps(key).set(tokenString,tokenValiditySeconds, TimeUnit.SECONDS);
Long expire = stringRedisTemplate.getExpire(key);
return expire>0;
}
//申请令牌
private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
URI uri = serviceInstance.getUri();
String url = uri+"/auth/oauth/token";
MultiValueMapbody = new LinkedMultiValueMap<>();
body.add("grant_type","password");
body.add("username",username);
body.add("password",password);
MultiValueMapheaders = new LinkedMultiValueMap<>();
headers.add("Authorization",this.getHttpBasic(clientId,clientSecret));
HttpEntity> requestEntity = new HttpEntity<>(body,headers);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401){
super.handleError(response);
}
}
});
ResponseEntitypackage com.xuecheng.auth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
//jwt令牌转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
UserDetailsService userDetailsService;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
TokenStore tokenStore;
@Autowired
private CustomUserAuthenticationConverter customUserAuthenticationConverter;
//读取密钥的配置
@Bean("keyProp")
public KeyProperties keyProperties(){
return new KeyProperties();
}
@Resource(name = "keyProp")
private KeyProperties keyProperties;
//客户端配置
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(this.dataSource).clients(this.clientDetails());
/* clients.inMemory()
.withClient("XcWebApp")//客户端id
.secret("XcWebApp")//密码,要保密
.accessTokenValiditySeconds(60)//访问令牌有效期
.refreshTokenValiditySeconds(60)//刷新令牌有效期
//授权客户端请求认证服务的类型authorization_code:根据授权码生成令牌,
// client_credentials:客户端认证,refresh_token:刷新令牌,password:密码方式认证
.authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token", "password")
.scopes("app");//客户端范围,名称自定义,必填*/
}
//token的存储方法
// @Bean
// public InMemoryTokenStore tokenStore() {
// //将令牌存储到内存
// return new InMemoryTokenStore();
// }
// @Bean
// public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory){
// RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
// return redisTokenStore;
// }
@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory
(keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray())
.getKeyPair(keyProperties.getKeyStore().getAlias(),keyProperties.getKeyStore().getPassword().toCharArray());
converter.setKeyPair(keyPair);
//配置自定义的CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
//授权服务器端点配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/*CollectiontokenEnhancers = applicationContext.getBeansOfType(TokenEnhancer.class).values(); */
TokenEnhancerChain tokenEnhancerChain=new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(new ArrayList<>(tokenEnhancers));
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setReuseRefreshToken(true);
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenStore(tokenStore);
defaultTokenServices.setAccessTokenValiditySeconds(1111111);
defaultTokenServices.setRefreshTokenValiditySeconds(1111111);
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
//.tokenStore(tokenStore);
.tokenServices(defaultTokenServices);
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.authenticationManager(authenticationManager)//认证管理器
.tokenStore(tokenStore)//令牌存储
.userDetailsService(userDetailsService);//用户信息service
}
//授权服务器的安全配置
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
// oauthServer.checkTokenAccess("isAuthenticated()");//校验token需要认证通过,可采用http basic认证
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}package com.xuecheng.auth.config;
import com.xuecheng.auth.service.UserJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Autowired
UserDetailsService userDetailsService;
@Override
public MapconvertUserAuthentication(Authentication authentication) {
LinkedHashMap response = new LinkedHashMap();
String name = authentication.getName();
response.put("user_name", name);
Object principal = authentication.getPrincipal();
UserJwt userJwt = null;
if(principal instanceof UserJwt){
userJwt = (UserJwt) principal;
}else{
//refresh_token默认不去调用userdetailService获取用户信息,这里我们手动去调用,得到 UserJwt
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
userJwt = (UserJwt) userDetails;
}
response.put("name", userJwt.getName());
response.put("id", userJwt.getId());
response.put("utype",userJwt.getUtype());
response.put("userpic",userJwt.getUserpic());
response.put("companyId",userJwt.getCompanyId());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}1 import org.springframework.context.annotation.Bean;
2 import org.springframework.context.annotation.Configuration;
3 import org.springframework.core.annotation.Order;
4 import org.springframework.security.authentication.AuthenticationManager;
5 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6 import org.springframework.security.config.annotation.web.builders.WebSecurity;
7 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
8 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
9 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
10 import org.springframework.security.crypto.password.PasswordEncoder;
11
12 @Configuration
13 @EnableWebSecurity
14 @Order(-1)
15 class WebSecurityConfig extends WebSecurityConfigurerAdapter {
16
17 @Override
18 public void configure(WebSecurity web) throws Exception {
19 web.ignoring().antMatchers("/userlogin","/userlogout","/userjwt");
20
21 }
22 @Bean
23 @Override
24 public AuthenticationManager authenticationManagerBean() throws Exception {
25 AuthenticationManager manager = super.authenticationManagerBean();
26 return manager;
27 }
28 //采用bcrypt对密码进行编码
29 @Bean
30 public PasswordEncoder passwordEncoder() {
31 return new BCryptPasswordEncoder();
32 }
33
34 @Override
35 public void configure(HttpSecurity http) throws Exception {
36 http.csrf().disable()
37 .httpBasic().and()
38 .formLogin()
39 .and()
40 .authorizeRequests().anyRequest().authenticated();
41
42 }
43 }package com.xuecheng.auth.service;
import com.alibaba.fastjson.JSON;
import com.xuecheng.filesystem.framework.client.XcServiceList;
import com.xuecheng.filesystem.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.filesystem.framework.domain.ucenter.response.AuthCode;
import com.xuecheng.filesystem.framework.exception.ExceptionCast;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class AuthService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${auth.tokenValiditySeconds}")
private long tokenValiditySeconds;
/**
* 用户认证登录,申请令牌
* @param username
* @param password
* @param clientId
* @param clientSecret
* @return
*/
public AuthToken login(String username, String password, String clientId, String clientSecret) {
//申请令牌
AuthToken authToken = this.applyToken(username,password,clientId,clientSecret);
//将令牌存入redis
boolean result = this.saveTokenToRedis(authToken);
if (!result){
//存入失败
ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
}
return authToken;
}
//存入redis
private boolean saveTokenToRedis(AuthToken authToken) {
String key = "user_token:"+authToken.getJti();
String tokenString = JSON.toJSONString(authToken);
stringRedisTemplate.boundValueOps(key).set(tokenString,tokenValiditySeconds, TimeUnit.SECONDS);
Long expire = stringRedisTemplate.getExpire(key);
return expire>0;
}
//申请令牌
private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
URI uri = serviceInstance.getUri();
String url = uri+"/auth/oauth/token";
MultiValueMapbody = new LinkedMultiValueMap<>();
body.add("grant_type","password");
body.add("username",username);
body.add("password",password);
MultiValueMapheaders = new LinkedMultiValueMap<>();
headers.add("Authorization",this.getHttpBasic(clientId,clientSecret));
HttpEntity> requestEntity = new HttpEntity<>(body,headers);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401){
super.handleError(response);
}
}
});
ResponseEntityimport lombok.Data;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
@Data
@ToString
public class UserJwt extends User {
private String id;
private String name;
private String userpic;
private String utype;
private String companyId;
public UserJwt(String username, String password, Collection extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
}server:
port: ${PORT:40400}
servlet:
context-path: /auth
spring:
application:
name: xc-service-ucenter-auth
redis:
host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:6379}
timeout: 5000 #连接超时 毫秒
jedis:
pool:
maxActive: 3
maxIdle: 3
minIdle: 1
maxWait: -1 #连接池最大等行时间 -1没有限制
datasource:
druid:
url: ${MYSQL_URL:jdbc:mysql://localhost:3306/xc_user?characterEncoding=utf-8}
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
initialSize: 5 #初始建立连接数量
minIdle: 5 #最小连接数量
maxActive: 20 #最大连接数量
maxWait: 10000 #获取连接最大等待时间,毫秒
testOnBorrow: true #申请连接时检测连接是否有效
testOnReturn: false #归还连接时检测连接是否有效
timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒)
auth:
tokenValiditySeconds: 1200 #token存储到redis的过期时间
clientId: XcWebApp
clientSecret: XcWebApp
cookieDomain: xuecheng.com
cookieMaxAge: -1
encrypt:
key-store:
location: classpath:/xc.keystore
secret: xuechengkeystore
alias: xckey
password: xuecheng
eureka:
client:
registerWithEureka: true #服务注册开关
fetchRegistry: true #服务发现开关
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: ${IP_ADDRESS:127.0.0.1}
instance-id: ${spring.application.name}:${server.port} #指定实例id
ribbon:
MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器
MaxAutoRetriesNextServer: 3 #切换实例的重试次数
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 6000 #请求处理的超时时间