项目的源码地址:https://github.com/daxian-zhu/online_edu

项目是最新的,文章可能不是最新的

这里先简单的介绍项目,不然有的地方可能我表达不清楚容易造成误解

online_edu
|
|----online_edu_com       公共模块,存放公共实体,枚举,工具类等
|
|----online_edu_config    配置中心,基于spring cloud config,有条件建议apollo配置中心
|
|----online_edu_eureka    注册中心,基于eureka
|
|----online_edu_gateway   网关,基于zuul
|
|----online_edu_oauth     认证中心,基于spring cloud oauth2
|
|----online_edu_user      用户,权限

这里主要讲的oauth模块的配置,用户部分会简单的带过。我这里的根据模块分库的,暂时没有做分表操作。

第一步:建表,这个其实spring已经提供了,我们只需要运行即可

DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (`token_id` varchar(256) DEFAULT NULL,`token` blob,`authentication_id` varchar(48) NOT NULL,`user_name` varchar(256) DEFAULT NULL,`client_id` varchar(256) DEFAULT NULL,`authentication` blob,`refresh_token` varchar(256) DEFAULT NULL,PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals` (`userId` varchar(256) DEFAULT NULL,`clientId` varchar(256) DEFAULT NULL,`scope` varchar(256) DEFAULT NULL,`status` varchar(10) DEFAULT NULL,`expiresAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,`lastModifiedAt` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (`client_id` varchar(48) NOT NULL,`resource_ids` varchar(256) DEFAULT NULL,`client_secret` varchar(256) DEFAULT NULL,`scope` varchar(256) DEFAULT NULL,`authorized_grant_types` varchar(256) DEFAULT NULL,`web_server_redirect_uri` varchar(256) DEFAULT NULL,`authorities` varchar(256) DEFAULT NULL,`access_token_validity` int(11) DEFAULT NULL,`refresh_token_validity` int(11) DEFAULT NULL,`additional_information` varchar(4096) DEFAULT NULL,`autoapprove` varchar(256) DEFAULT NULL,PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token` (`token_id` varchar(256) DEFAULT NULL,`token` blob,`authentication_id` varchar(48) NOT NULL,`user_name` varchar(256) DEFAULT NULL,`client_id` varchar(256) DEFAULT NULL,PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (`code` varchar(256) DEFAULT NULL,`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (`token_id` varchar(256) DEFAULT NULL,`token` blob,`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入一条相应的数据:

INSERT INTO `edu_oauth`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('daxian', NULL, '$2a$10$.8enc0qC92YpTnS7GR8MCO2yF33AGRRgpHtyshN48Os2gPLWQ4Sri', 'xx', 'authorization_code,password', 'https://www.baidu.com', NULL, NULL, NULL, NULL, NULL);

这里注意secret是加密了的。authorized_grant_types表示支持的授权模式,这里支持了authorization_code,password的2种模式。

第二步:POM文件改造

 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-bus</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-stream-binder-rabbit</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.1</version></dependency><!-- alibaba的druid数据库连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.9</version></dependency><!-- MySql数据库驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><dependency><groupId>com.clark</groupId><artifactId>online_edu_com</artifactId><version>${project.parent.version}</version></dependency>

其中online_edu_com就是项目中的com模块咯。

第三步:启动类改造

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class OAuthApplication {public static void main(String[] args) throws Exception {SpringApplication.run(OAuthApplication.class, args);}}

第四步:认证服务改造

/*** 认证服务器配置继承 JWT实现* * @author 大仙**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {/*** 认证管理,需要在websecurity里面重写实现*/@Autowiredprivate AuthenticationManager authenticationManager;/*** 数据库*/@Autowiredprivate DataSource dataSource;/*** 用户详情业务实现*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;@Beanpublic TokenStore tokenStore() {
//      return new RedisTokenStore(redisConnectionFactory);return new JwtTokenStore(jwtAccessTokenConverter());}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {//允许表单验证security.allowFormAuthenticationForClients();// 开启/oauth/token_key验证端口无权限访问        security.tokenKeyAccess("permitAll()");// 开启/oauth/check_token验证端口认证权限访问security.checkTokenAccess("isAuthenticated()");}/*** 配置 oauth_client_details【client_id和client_secret等】信息的认证【检查ClientDetails的合法性】服务* 设置 认证信息的来源:数据库 (可选项:数据库和内存,使用内存一般用来作测试) 自动注入:ClientDetailsService的实现类* JdbcClientDetailsService (检查 ClientDetails 对象)* 这个方法主要是用于校验注册的第三方客户端的信息,可以存储在数据库中,默认方式是存储在内存中,如下所示,注释掉的代码即为内存中存储的方式*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetails());}@Beanpublic ClientDetailsService clientDetails() {return new JdbcClientDetailsService(dataSource);}@Beanpublic WebResponseExceptionTranslator webResponseExceptionTranslator() {return new MssWebResponseExceptionTranslator();}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.userDetailsService(userDetailsService);endpoints.tokenServices(defaultTokenServices());// 认证异常翻译endpoints.exceptionTranslator(webResponseExceptionTranslator());}/*** <p>* 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,* </p>* 自定义的token 认证的token是存到redis里的* * @return*/@Primary@Beanpublic DefaultTokenServices defaultTokenServices() {DefaultTokenServices tokenServices = new DefaultTokenServices();//配置JwtAccessToken转换器tokenServices.setTokenEnhancer(jwtAccessTokenConverter());//认证管理tokenServices.setAuthenticationManager(authenticationManager);//token存储tokenServices.setTokenStore(tokenStore());//是否支付刷新tokenServices.setSupportRefreshToken(true);tokenServices.setClientDetailsService(clientDetails());// token有效期自定义设置,默认12小时tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);// refresh_token默认30天tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);return tokenServices;}/*** 使用非对称加密算法来对Token进行签名* @return*/@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() {final JwtAccessTokenConverter converter = new JwtAccessToken();// 导入证书KeyStoreKeyFactory keyStoreKeyFactory =new KeyStoreKeyFactory(new ClassPathResource("kevin_key.jks"), "daxianzuishuai".toCharArray());//别名converter.setKeyPair(keyStoreKeyFactory.getKeyPair("kevin_key"));return converter;}
}

这里采用jwt的签名方式,有几个类提供下:

public class JwtAccessToken extends JwtAccessTokenConverter{/*** 生成token* @param accessToken* @param authentication* @return*/@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);// 设置额外用户信息UC_User baseUser = ((BaseUserDetail) authentication.getPrincipal()).getBaseUser();baseUser.setPassword(null);// 将用户信息添加到token额外信息中defaultOAuth2AccessToken.getAdditionalInformation().put(Constant.USER_INFO, baseUser);return super.enhance(defaultOAuth2AccessToken, authentication);}/*** 解析token* @param value* @param map* @return*/@Overridepublic OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map){OAuth2AccessToken oauth2AccessToken = super.extractAccessToken(value, map);convertData(oauth2AccessToken, oauth2AccessToken.getAdditionalInformation());return oauth2AccessToken;}private void convertData(OAuth2AccessToken accessToken,  Map<String, ?> map) {accessToken.getAdditionalInformation().put(Constant.USER_INFO,convertUserData(map.get(Constant.USER_INFO)));}private UC_User convertUserData(Object map) {String json = JsonUtils.deserializer(map);UC_User user = JsonUtils.serializable(json, UC_User.class);return user;}
}
/***  重写RedisTokenStore* spring5.0修改了一些方法,导致不兼容,需要重写全部的set()为stringCommands().set()* @author 大仙**/
public class RedisTokenStore implements TokenStore {private static final String ACCESS = "access:";private static final String AUTH_TO_ACCESS = "auth_to_access:";private static final String AUTH = "auth:";private static final String REFRESH_AUTH = "refresh_auth:";private static final String ACCESS_TO_REFRESH = "access_to_refresh:";private static final String REFRESH = "refresh:";private static final String REFRESH_TO_ACCESS = "refresh_to_access:";private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";private static final String UNAME_TO_ACCESS = "uname_to_access:";private final RedisConnectionFactory connectionFactory;private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();private String prefix = "";public RedisTokenStore(RedisConnectionFactory connectionFactory) {this.connectionFactory = connectionFactory;}public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {this.authenticationKeyGenerator = authenticationKeyGenerator;}public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) {this.serializationStrategy = serializationStrategy;}public void setPrefix(String prefix) {this.prefix = prefix;}private RedisConnection getConnection() {return this.connectionFactory.getConnection();}private byte[] serialize(Object object) {return this.serializationStrategy.serialize(object);}private byte[] serializeKey(String object) {return this.serialize(this.prefix + object);}private OAuth2AccessToken deserializeAccessToken(byte[] bytes) {return (OAuth2AccessToken)this.serializationStrategy.deserialize(bytes, OAuth2AccessToken.class);}private OAuth2Authentication deserializeAuthentication(byte[] bytes) {return (OAuth2Authentication)this.serializationStrategy.deserialize(bytes, OAuth2Authentication.class);}private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) {return (OAuth2RefreshToken)this.serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class);}private byte[] serialize(String string) {return this.serializationStrategy.serialize(string);}private String deserializeString(byte[] bytes) {return this.serializationStrategy.deserializeString(bytes);}@Overridepublic OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {String key = this.authenticationKeyGenerator.extractKey(authentication);byte[] serializedKey = this.serializeKey(AUTH_TO_ACCESS + key);byte[] bytes = null;RedisConnection conn = this.getConnection();try {bytes = conn.get(serializedKey);} finally {conn.close();}OAuth2AccessToken accessToken = this.deserializeAccessToken(bytes);if (accessToken != null) {OAuth2Authentication storedAuthentication = this.readAuthentication(accessToken.getValue());if (storedAuthentication == null || !key.equals(this.authenticationKeyGenerator.extractKey(storedAuthentication))) {this.storeAccessToken(accessToken, authentication);}}return accessToken;}@Overridepublic OAuth2Authentication readAuthentication(OAuth2AccessToken token) {return this.readAuthentication(token.getValue());}@Overridepublic OAuth2Authentication readAuthentication(String token) {byte[] bytes = null;RedisConnection conn = this.getConnection();try {bytes = conn.get(this.serializeKey("auth:" + token));} finally {conn.close();}OAuth2Authentication auth = this.deserializeAuthentication(bytes);return auth;}@Overridepublic OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {return this.readAuthenticationForRefreshToken(token.getValue());}public OAuth2Authentication readAuthenticationForRefreshToken(String token) {RedisConnection conn = getConnection();try {byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));OAuth2Authentication auth = deserializeAuthentication(bytes);return auth;} finally {conn.close();}}@Overridepublic void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {byte[] serializedAccessToken = serialize(token);byte[] serializedAuth = serialize(authentication);byte[] accessKey = serializeKey(ACCESS + token.getValue());byte[] authKey = serializeKey(AUTH + token.getValue());byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());RedisConnection conn = getConnection();try {conn.openPipeline();conn.stringCommands().set(accessKey, serializedAccessToken);conn.stringCommands().set(authKey, serializedAuth);conn.stringCommands().set(authToAccessKey, serializedAccessToken);if (!authentication.isClientOnly()) {conn.rPush(approvalKey, serializedAccessToken);}conn.rPush(clientId, serializedAccessToken);if (token.getExpiration() != null) {int seconds = token.getExpiresIn();conn.expire(accessKey, seconds);conn.expire(authKey, seconds);conn.expire(authToAccessKey, seconds);conn.expire(clientId, seconds);conn.expire(approvalKey, seconds);}OAuth2RefreshToken refreshToken = token.getRefreshToken();if (refreshToken != null && refreshToken.getValue() != null) {byte[] refresh = serialize(token.getRefreshToken().getValue());byte[] auth = serialize(token.getValue());byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());conn.stringCommands().set(refreshToAccessKey, auth);byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue());conn.stringCommands().set(accessToRefreshKey, refresh);if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;Date expiration = expiringRefreshToken.getExpiration();if (expiration != null) {int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L).intValue();conn.expire(refreshToAccessKey, seconds);conn.expire(accessToRefreshKey, seconds);}}}conn.closePipeline();} finally {conn.close();}}private static String getApprovalKey(OAuth2Authentication authentication) {String userName = authentication.getUserAuthentication() == null ? "": authentication.getUserAuthentication().getName();return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);}private static String getApprovalKey(String clientId, String userName) {return clientId + (userName == null ? "" : ":" + userName);}@Overridepublic void removeAccessToken(OAuth2AccessToken accessToken) {this.removeAccessToken(accessToken.getValue());}@Overridepublic OAuth2AccessToken readAccessToken(String tokenValue) {byte[] key = serializeKey(ACCESS + tokenValue);byte[] bytes = null;RedisConnection conn = getConnection();try {bytes = conn.get(key);} finally {conn.close();}OAuth2AccessToken accessToken = deserializeAccessToken(bytes);return accessToken;}public void removeAccessToken(String tokenValue) {byte[] accessKey = serializeKey(ACCESS + tokenValue);byte[] authKey = serializeKey(AUTH + tokenValue);byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);RedisConnection conn = getConnection();try {conn.openPipeline();conn.get(accessKey);conn.get(authKey);conn.del(accessKey);conn.del(accessToRefreshKey);// Don't remove the refresh token - it's up to the caller to do thatconn.del(authKey);List<Object> results = conn.closePipeline();byte[] access = (byte[]) results.get(0);byte[] auth = (byte[]) results.get(1);OAuth2Authentication authentication = deserializeAuthentication(auth);if (authentication != null) {String key = authenticationKeyGenerator.extractKey(authentication);byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key);byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());conn.openPipeline();conn.del(authToAccessKey);conn.lRem(unameKey, 1, access);conn.lRem(clientId, 1, access);conn.del(serialize(ACCESS + key));conn.closePipeline();}} finally {conn.close();}}@Overridepublic void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue());byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue());byte[] serializedRefreshToken = serialize(refreshToken);RedisConnection conn = getConnection();try {conn.openPipeline();conn.stringCommands().set(refreshKey, serializedRefreshToken);conn.stringCommands().set(refreshAuthKey, serialize(authentication));if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;Date expiration = expiringRefreshToken.getExpiration();if (expiration != null) {int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L).intValue();conn.expire(refreshKey, seconds);conn.expire(refreshAuthKey, seconds);}}conn.closePipeline();} finally {conn.close();}}@Overridepublic OAuth2RefreshToken readRefreshToken(String tokenValue) {byte[] key = serializeKey(REFRESH + tokenValue);byte[] bytes = null;RedisConnection conn = getConnection();try {bytes = conn.get(key);} finally {conn.close();}OAuth2RefreshToken refreshToken = deserializeRefreshToken(bytes);return refreshToken;}@Overridepublic void removeRefreshToken(OAuth2RefreshToken refreshToken) {this.removeRefreshToken(refreshToken.getValue());}public void removeRefreshToken(String tokenValue) {byte[] refreshKey = serializeKey(REFRESH + tokenValue);byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue);byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue);byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);RedisConnection conn = getConnection();try {conn.openPipeline();conn.del(refreshKey);conn.del(refreshAuthKey);conn.del(refresh2AccessKey);conn.del(access2RefreshKey);conn.closePipeline();} finally {conn.close();}}@Overridepublic void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {this.removeAccessTokenUsingRefreshToken(refreshToken.getValue());}private void removeAccessTokenUsingRefreshToken(String refreshToken) {byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken);List<Object> results = null;RedisConnection conn = getConnection();try {conn.openPipeline();conn.get(key);conn.del(key);results = conn.closePipeline();} finally {conn.close();}if (results == null) {return;}byte[] bytes = (byte[]) results.get(0);String accessToken = deserializeString(bytes);if (accessToken != null) {removeAccessToken(accessToken);}}@Overridepublic Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName));List<byte[]> byteList = null;RedisConnection conn = getConnection();try {byteList = conn.lRange(approvalKey, 0, -1);} finally {conn.close();}if (byteList == null || byteList.size() == 0) {return Collections.<OAuth2AccessToken> emptySet();}List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());for (byte[] bytes : byteList) {OAuth2AccessToken accessToken = deserializeAccessToken(bytes);accessTokens.add(accessToken);}return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);}@Overridepublic Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId);List<byte[]> byteList = null;RedisConnection conn = getConnection();try {byteList = conn.lRange(key, 0, -1);} finally {conn.close();}if (byteList == null || byteList.size() == 0) {return Collections.<OAuth2AccessToken> emptySet();}List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());for (byte[] bytes : byteList) {OAuth2AccessToken accessToken = deserializeAccessToken(bytes);accessTokens.add(accessToken);}return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);}
}
/*** 包装org.springframework.security.core.userdetails.User类* @author 大仙**/
public class BaseUserDetail implements UserDetails, CredentialsContainer {/*** */private static final long serialVersionUID = 1L;/*** 用户*/private final UC_User baseUser;private final User user;public BaseUserDetail(UC_User baseUser, User user) {this.baseUser = baseUser;this.user = user;}@Overridepublic void eraseCredentials() {user.eraseCredentials();}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return user.getAuthorities();}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return user.isAccountNonExpired();}@Overridepublic boolean isAccountNonLocked() {return user.isAccountNonLocked();}@Overridepublic boolean isCredentialsNonExpired() {return user.isCredentialsNonExpired();}@Overridepublic boolean isEnabled() {return user.isEnabled();}public UC_User getBaseUser() {return baseUser;}
}
/*** 异常翻译* @author 大仙**/
public class MssWebResponseExceptionTranslator implements WebResponseExceptionTranslator {private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();@Overridepublic ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {// Try to extract a SpringSecurityException from the stacktraceThrowable[] causeChain = throwableAnalyzer.determineCauseChain(e);Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);if (ase != null) {return handleOAuth2Exception((OAuth2Exception) ase);}ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class,causeChain);if (ase != null) {return handleOAuth2Exception(new MssWebResponseExceptionTranslator.UnauthorizedException(e.getMessage(), e));}ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);if (ase instanceof AccessDeniedException) {return handleOAuth2Exception(new MssWebResponseExceptionTranslator.ForbiddenException(ase.getMessage(), ase));}ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);if (ase instanceof HttpRequestMethodNotSupportedException) {return handleOAuth2Exception(new MssWebResponseExceptionTranslator.MethodNotAllowed(ase.getMessage(), ase));}return handleOAuth2Exception(new MssWebResponseExceptionTranslator.ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));}private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {int status = e.getHttpErrorCode();HttpHeaders headers = new HttpHeaders();headers.set("Cache-Control", "no-store");headers.set("Pragma", "no-cache");if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));}ResponseEntity<OAuth2Exception> response = new ResponseEntity<OAuth2Exception>(e, headers, HttpStatus.valueOf(status));return response;}public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {this.throwableAnalyzer = throwableAnalyzer;}@SuppressWarnings("serial")private static class ForbiddenException extends OAuth2Exception {public ForbiddenException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "access_denied";}@Overridepublic int getHttpErrorCode() {return 403;}}@SuppressWarnings("serial")private static class ServerErrorException extends OAuth2Exception {public ServerErrorException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "server_error";}@Overridepublic int getHttpErrorCode() {return 500;}}@SuppressWarnings("serial")private static class UnauthorizedException extends OAuth2Exception {public UnauthorizedException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "unauthorized";}@Overridepublic int getHttpErrorCode() {return 401;}}@SuppressWarnings("serial")private static class MethodNotAllowed extends OAuth2Exception {public MethodNotAllowed(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "method_not_allowed";}@Overridepublic int getHttpErrorCode() {return 405;}}
}

第五步:spring security改造

/*** 配置spring security* ResourceServerConfig 是比SecurityConfig 的优先级低的* @author 大仙**/
@Configuration
@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 用户详情业务实现*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 重新实例化bean*/@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http    // 配置登陆页/login并允许访问.formLogin().permitAll()// 其余所有请求全部需要鉴权认证.and().authorizeRequests().anyRequest().authenticated()// 由于使用的是JWT,我们这里不需要csrf.and().csrf().disable();}/*** 用户验证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(daoAuthenticationProvider());}/*** 加密方式* @return*/@Beanpublic static BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic DaoAuthenticationProvider daoAuthenticationProvider(){DaoAuthenticationProvider provider = new DaoAuthenticationProvider();// 设置userDetailsServiceprovider.setUserDetailsService(userDetailsService);// 禁止隐藏用户未找到异常provider.setHideUserNotFoundExceptions(false);// 使用BCrypt进行密码的hashprovider.setPasswordEncoder(passwordEncoder());return provider;}/*** 过滤*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/css/**", "/js/**", "/plugins/**", "/favicon.ico");}
}

第六步:UserDetailsServiceImpl实现自定义的userDetals

/*** 用户详情业务实现* @author 大仙**/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {/*** 用户业务接口*/@Autowiredprivate UserService userService;@Autowiredprivate RoleService roleService;
//    @Autowired
//    private PermissionService permissionService;@Overridepublic BaseUserDetail loadUserByUsername(String username) throws UsernameNotFoundException {Result<UC_User> userResult = userService.findByUsername(username);//判断是否请求成功if (userResult.getCode() != ResponeCode.OK.getCode()) {throw new UsernameNotFoundException("用户:" + username + ",不存在!");}//权限集合Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();boolean enabled = true; // 可用性 :true:可用 false:不可用boolean accountNonExpired = true; // 过期性 :true:没过期 false:过期boolean credentialsNonExpired = true; // 有效性 :true:凭证有效 false:凭证无效boolean accountNonLocked = true; // 锁定性 :true:未锁定 false:已锁定UC_User user = new UC_User();BeanUtils.copyProperties(userResult.getData(),user);Result<List<UC_Role>> roleResult = roleService.getRoleByUserId(user.getId());if (roleResult.getCode() != ResponeCode.OK.getCode()){List<UC_Role> roleVoList = roleResult.getData();for (UC_Role role:roleVoList){//角色必须是ROLE_开头,可以在数据库中设置GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+role.getValue());grantedAuthorities.add(grantedAuthority);
//                //获取权限
//                Result<List<MenuVo>> perResult  = permissionService.getRolePermission(role.getId());
//                if (perResult.getCode() != StatusCode.SUCCESS_CODE){
//                    List<MenuVo> permissionList = perResult.getData();
//                    for (MenuVo menu:permissionList
//                            ) {
//                        GrantedAuthority authority = new SimpleGrantedAuthority(menu.getCode());
//                        grantedAuthorities.add(authority);
//                    }
//                }}}User ss_user = new User(user.getUsername(), user.getPassword(),enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);return new BaseUserDetail(user,ss_user);}
}
/*** 用户业务接口* @author 大仙**/
@FeignClient(name = "user-center",fallback = UserServiceImpl.class)
public interface UserService {/*** 根据用户名* @param username* @return*/@GetMapping("user/{username}")public Result<UC_User> findByUsername(@PathVariable("username") String username);}
/*** 角色业务接口* @author 大仙**/
@FeignClient(name = "user-center",fallback = RoleServiceImpl.class)
public interface RoleService {/*** 根据用户id查询用户拥有的角色列表* @param userId* @return*/@GetMapping("role/{userId}")public Result<List<UC_Role>> getRoleByUserId(@PathVariable("userId") String userId);}

第七步:配置文件配置

#端口,正式环境由外部参数指定
server:port: 9004
#应用名称
spring:application:name: public-oauthcloud:config:profile: ${spring.profiles.active}name: ${spring.application.name},common,rabbitmq,redislabel: masterdiscovery: enabled: trueservice-id: public-config
eureka:instance:#使用IP注册prefer-ip-address: true#ip-address: 192.168.1.1 #强制指定IP地址,默认会获取本机的IP地址instance-id: ${spring.cloud.client.ip-address}:${server.port}hostname: ${spring.cloud.client.ip-address}#客户端向服务端心跳间隔lease-renewal-interval-in-seconds: 3client:#eureka client间隔多久去拉取服务注册信息,默认为30秒。对于网关或者中心服务可以设置小一点registry-fetch-interval-seconds: 3service-url:defaultZone: http://127.0.0.1:9001/eureka/
spring:#redis配置redis:host: XXXpassword: XXXport: 6379database: 0timeout: 60000
spring:datasource:name: druidDataSourcetype: com.alibaba.druid.pool.DruidDataSourcedruid:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://47.95.250.218:3306/edu_oauth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=trueusername: edu_oauthpassword: edu.,Dev321initialSize: 5 #初始化连接大小minIdle: 5     #最小连接池数量maxActive: 20  #最大连接池数量maxWait: 60000 #获取连接时最大等待时间,单位毫秒timeBetweenEvictionRunsMillis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒minEvictableIdleTimeMillis: 300000   #配置一个连接在池中最小生存的时间,单位是毫秒validationQuery: SELECT 1 from DUAL  #测试连接testWhileIdle: true                  #申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性testOnBorrow: false                  #获取连接时执行检测,建议关闭,影响性能testOnReturn: false                  #归还连接时执行检测,建议关闭,影响性能poolPreparedStatements: false        #是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭maxPoolPreparedStatementPerConnectionSize: 20 #开启poolPreparedStatements后生效filters: stat,wall,log4j #配置扩展插件,常用的插件有=>stat:监控统计  log4j:日志  wall:防御sql注入connectionProperties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' #通过connectProperties属性来打开mergeSql功能;慢SQL记录

用户中心的配置我这里就不说了,就简单的查询和接口实现。

依次启动eureka,config,user_center,oauth

下面是获取认证操作。

第一步:请求code

http://localhost:9004/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://baidu.com

请求参数说明:

response_type:code代表获取code值
client_id:客户端的ID,必选项  
redirect_uri:重定向URI,必选项  
scope:申请的权限范围,可选项  
state:任意值,认证服务器会原样返回,用于抵制CSRF(跨站请求伪造)攻击。

如果没有登录,会调到登录页面

账号密码就是刚刚存内存里面的,daxian  123

登录成功之后跳转授权页面,选择授权之后会重定向到baidu

到此code获取完毕。

第二步:根据code获取access_token

请求参数说明:

client_id:客户端的ID,必选项。  
client_secret:客户端的密钥,必选项。  
grant_type:表示使用的授权模式,必选项。 
redirect_uri:当前版本必选项
code: 当前获取的code值
好了,简单的授权模式就完成了,下一篇会讲到怎么使用数据库,redis来实现用户和token存储。

spring cloud oauth2系列篇(二)深入authorization_code授权码模式完整实现相关推荐

  1. spring cloud oauth2系列篇(三)password模式获取access_token

    项目的源码地址:https://github.com/daxian-zhu/online_edu password模式和上一篇文章的代码是一致的: https://blog.csdn.net/zhuw ...

  2. 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结

    在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. ...

  3. OAuth2.0 - 介绍与使用 及 授权码模式讲解

    一.OAuth2.0 前面我们已经学习了SpringSecurity在SpringMVC环境下和WebFlux环境下用户认证授权以及整合JWT作为Token无状态认证授权,但是在前面的演示中都会发现全 ...

  4. Spring Cloud OAuth2 实现用户认证及单点登录

    OAuth 2 有四种授权模式,分别是授权码模式(authorization code).简化模式(implicit).密码模式(resource owner password credentials ...

  5. java调用授权接口oauth2_微信授权就是这个原理,Spring Cloud OAuth2 授权码模式

    上一篇文章Spring Cloud OAuth2 实现单点登录介绍了使用 password 模式进行身份认证和单点登录.本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式 ...

  6. 面试官:能说一说微信授权的原理吗?(Spring Cloud OAuth2 授权码模式)

    我是风筝,公众号「古时的风筝」,一个简单的程序员鼓励师. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 上一篇文章Spring Cloud OA ...

  7. 11. Spring Cloud OAuth2 (2刷)

    oath2 基础知识 授权节点 /oauth/authorize 获得token节点 /oauth/token 浏览器 - 向 授权服务器 请求token 授权服务器 返回token 授权服务器为 a ...

  8. Owin + WebApi + OAuth2 搭建授权模式(授权码模式 Part I)

    绪 最近想要整理自己代码封装成库,也十分想把自己的设计思路贴出来让大家指正,奈何时间真的不随人意. 想要使用 OWIN 做中间件服务,该服务中包含 管线.授权 两部分.于是决定使用 webapi .O ...

  9. Spring Security Oauth2系列(七)

    摘要: 今天来给大家分享一下期待已久的oauth2回调地址的设置,相信接触过oauth2的很多coder已经按捺不住激动的心情了吧.因为这个回调地址的配置能够让授权码模式的运用的主动权掌握在自己的手中 ...

最新文章

  1. [物理学与PDEs]第1章第7节 媒质中的 Maxwell 方程组 7.2 媒质交界面上的条件
  2. linux shell 合并文本
  3. beacon帧字段结构最全总结(三)——VHT字段总结
  4. 图形驱动程序和显卡驱动什么区别_什么是核心显卡,核心显卡和集成显卡的区别...
  5. java双机调度_Haproxy+keepalive-群集架构实验
  6. 大数问题(高精度运算)
  7. 通过PyMySQL连接MySQL
  8. .NET Core开发实战(第11课:文件配置提供程序)--学习笔记
  9. 扎心了,程序员2017到2019经历了什么?
  10. BaseActivity与BaseFragment的封装
  11. 端上智能——深度学习模型压缩与加速
  12. TCP连接(Time_Wait、Close_Wait)说明
  13. 打擦边球,涨粉1700万!中国最“不正经”的官媒,比杜蕾斯还会玩
  14. 【Sofa】Sofa比赛成绩记录
  15. 学生信息管理系统(附运行效果图和源码下载)分页技术(后台封装json数据传递到前端显示,动态分页等)(Mybatis,json,ajax,jQuery实用整合示例)
  16. Nginx 关于 location 的匹配规则详解
  17. 2019年计算机考研408历年真题2009-2019下载免费下载
  18. Java IO流关闭顺序
  19. 【亲自动手试验过的】硬盘免光驱安装Fedora5
  20. 分享8个强大的黑帽子自学网站(附:最常用的9种工具)

热门文章

  1. html6能代替原生app,网页转APP工具能否取代原生APP?这个在线制作平台,让移动开发更简单...
  2. vue完整项目,实现即可上岗web前端。
  3. windows workflow foundation(WWF)学习记录
  4. JS类教程 Lynda中文
  5. 犀浦某校一名计算机系大二男生,大学生恋爱的案例分析
  6. 关于虚拟机上fedora14不能上网的问题解决
  7. 高薪诚聘中高级软件工程师
  8. Java虚拟机学习笔记(一)—Java虚拟机概述
  9. node.js里的天龙八部
  10. 知乎万赞:计算机应届生月薪大多是多少?