受权码模式应用是最宽泛,而且大多数互联网站点都应用了此模式,如第三方应用 QQ 和微信登录。
一、Spring Security OAuth2 表构造
受权码模式用到了四张表,这里贴上 Oracle 版本的脚本。
-- OAUTH2.0 须要的 4 张表
CREATE TABLE OAUTH_CLIENT_DETAILS
(CLIENT_ID VARCHAR2(128) NOT NULL
CONSTRAINT PK_OAUTH_CLIENT_DETAILS
PRIMARY KEY,
RESOURCE_IDS VARCHAR2(128) DEFAULT NULL,
CLIENT_SECRET VARCHAR2(128) DEFAULT NULL,
SCOPE VARCHAR2(128) DEFAULT NULL,
AUTHORIZED_GRANT_TYPES VARCHAR2(128) DEFAULT NULL,
WEB_SERVER_REDIRECT_URI VARCHAR2(1024) DEFAULT NULL,
AUTHORITIES VARCHAR2(128) DEFAULT NULL,
ACCESS_TOKEN_VALIDITY NUMBER(11) DEFAULT NULL,
REFRESH_TOKEN_VALIDITY NUMBER(11) DEFAULT NULL,
ADDITIONAL_INFORMATION VARCHAR2(4000) DEFAULT NULL,
AUTOAPPROVE VARCHAR2(128) DEFAULT NULL
);
COMMENT ON TABLE OAUTH_CLIENT_DETAILS IS '利用表';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.CLIENT_ID IS '利用 ID';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.RESOURCE_IDS IS '受权资源 ID 汇合';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.CLIENT_SECRET IS '利用密钥';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.SCOPE IS '受权作用域';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.AUTHORIZED_GRANT_TYPES IS '受权容许类型';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.WEB_SERVER_REDIRECT_URI IS '受权回调地址';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.AUTHORITIES IS '领有权限汇合';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.ACCESS_TOKEN_VALIDITY IS 'ACCESS_TOKEN 有效期(秒)';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.REFRESH_TOKEN_VALIDITY IS 'REFRESH_TOKEN 有效期(秒)';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.ADDITIONAL_INFORMATION IS '附加信息(预留)';
COMMENT ON COLUMN OAUTH_CLIENT_DETAILS.AUTOAPPROVE IS '主动批准受权(TRUE,FALSE,READ,WRITE)';
CREATE TABLE OAUTH_CODE
(CODE VARCHAR2(128) DEFAULT NULL,
AUTHENTICATION BLOB
);
CREATE INDEX IX_OAUTH_CODE_CODE ON OAUTH_CODE (CODE);
COMMENT ON TABLE OAUTH_CODE IS '受权码表';
COMMENT ON COLUMN OAUTH_CODE.CODE IS '受权码';
COMMENT ON COLUMN OAUTH_CODE.AUTHENTICATION IS '身份验证信息';
CREATE TABLE OAUTH_ACCESS_TOKEN
(AUTHENTICATION_ID VARCHAR2(128) NOT NULL
CONSTRAINT PK_OAUTH_ACCESS_TOKEN
PRIMARY KEY,
TOKEN_ID VARCHAR2(128) DEFAULT NULL,
TOKEN BLOB,
USER_NAME VARCHAR2(128) DEFAULT NULL,
CLIENT_ID VARCHAR2(128) DEFAULT NULL,
AUTHENTICATION BLOB,
REFRESH_TOKEN VARCHAR2(128) DEFAULT NULL
);
CREATE INDEX IX_OAT_TOKEN_ID ON OAUTH_ACCESS_TOKEN (TOKEN_ID);
CREATE INDEX IX_OAT_USER_NAME ON OAUTH_ACCESS_TOKEN (USER_NAME);
CREATE INDEX IX_OAT_CLIENT_ID ON OAUTH_ACCESS_TOKEN (CLIENT_ID);
CREATE INDEX IX_OAT_REFRESH_TOKEN ON OAUTH_ACCESS_TOKEN (REFRESH_TOKEN);
COMMENT ON TABLE OAUTH_ACCESS_TOKEN IS '受权 TOKEN 表';
COMMENT ON COLUMN OAUTH_ACCESS_TOKEN.AUTHENTICATION_ID IS '身份验证 ID';
COMMENT ON COLUMN OAUTH_ACCESS_TOKEN.TOKEN_ID IS 'ACCESS_TOKEN 加密值';
COMMENT ON COLUMN OAUTH_ACCESS_TOKEN.TOKEN IS 'ACCESS_TOKEN 实在值';
COMMENT ON COLUMN OAUTH_ACCESS_TOKEN.USER_NAME IS '用户名';
COMMENT ON COLUMN OAUTH_ACCESS_TOKEN.CLIENT_ID IS '利用 ID';
COMMENT ON COLUMN OAUTH_ACCESS_TOKEN.AUTHENTICATION IS '身份验证信息';
COMMENT ON COLUMN OAUTH_ACCESS_TOKEN.REFRESH_TOKEN IS 'REFRESH_TOKEN 加密值';
CREATE TABLE OAUTH_REFRESH_TOKEN
(TOKEN_ID VARCHAR2(128) DEFAULT NULL,
TOKEN BLOB,
AUTHENTICATION BLOB
);
CREATE INDEX IX_ORT_TOKEN_ID ON OAUTH_REFRESH_TOKEN (TOKEN_ID);
COMMENT ON TABLE OAUTH_REFRESH_TOKEN IS '刷新 TOKEN 表';
COMMENT ON COLUMN OAUTH_REFRESH_TOKEN.TOKEN_ID IS 'ACCESS_TOKEN 加密值';
COMMENT ON COLUMN OAUTH_REFRESH_TOKEN.TOKEN IS 'ACCESS_TOKEN 实在值';
COMMENT ON COLUMN OAUTH_REFRESH_TOKEN.AUTHENTICATION IS '身份验证信息';
二、服务端实现
1、Maven 援用
springboot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/>
</parent>
spring security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
2、配置 Security
WebSecurityConfiguration.java
package com.leven.platform.auth.security;
import com.leven.commons.core.util.BeanMapper;
import com.leven.commons.model.exception.BasicEcode;
import com.leven.platform.auth.security.custom.CustomAuthenticationFilter;
import com.leven.platform.auth.security.custom.CustomAuthenticationProvider;
import com.leven.platform.auth.security.custom.CustomResponse;
import com.leven.platform.model.constant.PlatformConstant;
import com.leven.platform.model.constant.PlatformExceptionCode;
import com.leven.platform.model.pojo.dto.PlatformUserDTO;
import com.leven.platform.model.pojo.security.PlatformUserDetails;
import com.leven.platform.service.properties.WebProperties;
import com.leven.platform.service.security.encoder.ClientSecretEncoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import java.util.HashMap;
import java.util.Map;
/**
* spring security 外围配置
*
* @author Leven
*/
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(2)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 用户登录的图形验证码
*/
public static final String USER_LOGIN_VERIFY_CODE_KEY = "user_login_verify_code";
/**
* 用户登录的图形验证码过期工夫
*/
public static final String USER_LOGIN_VERIFY_CODE_EXPIRED_KEY = "user_login_verify_code_expired";
@Autowired
private WebProperties webProperties;
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {Map<String, PasswordEncoder> idToPasswordEncoder = new HashMap<>();
idToPasswordEncoder.put(PlatformConstant.ID_FOR_ENCODE_DEFAULT, new BCryptPasswordEncoder());
idToPasswordEncoder.put(ClientSecretEncoder.ID_FOR_ENCODE, new ClientSecretEncoder());
return new DelegatingPasswordEncoder(PlatformConstant.ID_FOR_ENCODE_DEFAULT, idToPasswordEncoder);
}
/**
* 注册自定义的 UsernamePasswordAuthenticationFilter
*
* @return
* @throws Exception
*/
@Bean
public CustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
// 认证胜利解决
filter.setAuthenticationSuccessHandler((request, response, authentication) -> {PlatformUserDetails principal = (PlatformUserDetails) authentication.getPrincipal();
log.info("用户 [{}] 登录胜利!", principal.getTrueName());
PlatformUserDTO platformUserDTO = BeanMapper.map(principal, PlatformUserDTO.class);
// 如果会话中有缓存申请,前端登录胜利后应重定向到原地址
RequestCache cache = new HttpSessionRequestCache();
SavedRequest savedRequest = cache.getRequest(request, response);
if (savedRequest != null) {String url = savedRequest.getRedirectUrl();
if (StringUtils.isNotBlank(url)) {platformUserDTO.setRedirectUrl(url);
}
}
CustomResponse.success(response, platformUserDTO);
});
// 认证失败解决
filter.setAuthenticationFailureHandler((request, response, exception) -> {log.info("登录失败:", exception);
if (exception instanceof UsernameNotFoundException) {CustomResponse.error(response, PlatformExceptionCode.USERNAME_NOT_FOUND);
} else if (exception instanceof DisabledException) {CustomResponse.error(response, PlatformExceptionCode.ACCOUNT_DISABLED);
} else if (exception instanceof AccountExpiredException) {CustomResponse.error(response, PlatformExceptionCode.ACCOUNT_EXPIRED);
} else if (exception instanceof LockedException) {CustomResponse.error(response, PlatformExceptionCode.ACCOUNT_LOCKED);
} else if (exception instanceof CredentialsExpiredException) {CustomResponse.error(response, PlatformExceptionCode.CREDENTIALS_EXPIRED);
} else if (exception instanceof AuthenticationServiceException) {CustomResponse.error(response, PlatformExceptionCode.AUTHENTICATION_ERROR);
} else if (exception instanceof BadCredentialsException) {CustomResponse.error(response, PlatformExceptionCode.BAD_CREDENTIALS);
} else if (exception instanceof AuthenticationCredentialsNotFoundException) {CustomResponse.error(response, PlatformExceptionCode.CHECK_CA_ERROR, exception.getMessage());
} else {CustomResponse.error(response, BasicEcode.USER_ERR_UNLOGINED);
}
});
// 登录解决 url
filter.setFilterProcessesUrl("/user/login");
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {auth.authenticationProvider(customAuthenticationProvider);
}
/**
* 设置不须要拦挡的动态资源
*
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) {web.ignoring().antMatchers("/static/**");
}
@Override
public void configure(HttpSecurity http) throws Exception {http.requestMatchers().antMatchers("/user/**", "/admin/**", "/oauth/authorize")
.and().authorizeRequests()
// 不须要拦挡的申请
.antMatchers("/user/login", "/user/verifyCode",
"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**").permitAll()
// 须要登录能力拜访
.antMatchers("/user/**").hasRole("USER")
// 须要管理员能力拜访
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().formLogin().loginPage(webProperties.getLoginFormUrl()).permitAll()
.and().logout().logoutUrl("/user/logout").logoutSuccessHandler((request, response, auth) -> {CustomResponse.success(response, null);
})
.and().exceptionHandling().authenticationEntryPoint((request, response, authException) ->
CustomResponse.error(response, BasicEcode.USER_ERR_UNLOGINED)
)
// 没有权限,返回异样信息
.accessDeniedHandler((request, response, authException) ->
CustomResponse.error(response, BasicEcode.PERMISSION_DENIED)
)
// CSRF 防护:与前端联调存在跨域,须要禁用 csrf().disable()
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// 增加自定义受权认证拦截器
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
WebProperties.java
package com.leven.platform.service.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* web 配置
*/
@Component
@ConfigurationProperties(prefix = "web")
@Data
public class WebProperties {
/**
* 用户登陆地址
*/
private String loginFormUrl;
/**
* 启用图形验证码
*/
private Boolean enableVerifyCode;
}
CustomAuthenticationProvider.java
package com.leven.platform.auth.security.custom;
import com.leven.platform.model.constant.PlatformExceptionCode;
import com.leven.platform.model.pojo.security.PlatformUserDetails;
import com.leven.platform.service.PlatformUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 自定义认证服务实现
* @author Leven
*/
@Slf4j
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private PlatformUserService platformUserService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String username = token.getName();
AuthenticationBean loginBean = (AuthenticationBean) token.getCredentials();
PlatformUserDetails userDetails = null;
if (username != null) {userDetails = (PlatformUserDetails) platformUserService.loadUserByUsername(username);
}
if (userDetails == null) {throw new UsernameNotFoundException(PlatformExceptionCode.USERNAME_NOT_FOUND);
} else if (!userDetails.isEnabled()) {throw new DisabledException(PlatformExceptionCode.ACCOUNT_DISABLED);
} else if (!userDetails.isAccountNonExpired()) {throw new AccountExpiredException(PlatformExceptionCode.ACCOUNT_EXPIRED);
} else if (!userDetails.isAccountNonLocked()) {throw new LockedException(PlatformExceptionCode.ACCOUNT_LOCKED);
} else if (!userDetails.isCredentialsNonExpired()) {throw new CredentialsExpiredException(PlatformExceptionCode.CREDENTIALS_EXPIRED);
}
// 校验明码
checkPwd(loginBean, userDetails);
// 受权
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> aClass) {
// 返回 true 后才会执行下面的 authenticate 办法, 这步能确保 authentication 能正确转换类型
return UsernamePasswordAuthenticationToken.class.equals(aClass);
}
/**
* 明码验证
* @param loginBean
* @param userDetails
*/
private void checkPwd(AuthenticationBean loginBean, PlatformUserDetails userDetails) {Date pwdExpired = userDetails.getPwdExpired();
// 用户输出的明文
String plainPwd = loginBean.getPassword();
// 数据库存储的密文
String password = userDetails.getPassword();
if (System.currentTimeMillis() > pwdExpired.getTime()) {throw new CredentialsExpiredException(PlatformExceptionCode.CREDENTIALS_EXPIRED);
}
// 校验明码是否正确
if (!passwordEncoder.matches(plainPwd, password)) {throw new BadCredentialsException(PlatformExceptionCode.BAD_CREDENTIALS);
}
}
}
PlatformUserService.java
package com.leven.platform.service;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* 用户信息接口
* @author Leven
* @date 2019-05-20
*/
public interface PlatformUserService extends UserDetailsService {
}
PlatformUserServiceImpl.java
package com.leven.platform.service.impl;
import com.leven.platform.core.mapper.PlatformUserMapper;
import com.leven.platform.model.enums.UsedEnum;
import com.leven.platform.model.pojo.dto.PlatformUserDTO;
import com.leven.platform.model.pojo.query.PlatformUserQuery;
import com.leven.platform.model.pojo.security.PlatformUserDetails;
import com.leven.platform.service.PlatformUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 用户信息接口实现
* @author Leven
* @date 2019-05-20
*/
@Slf4j
@Service
public class PlatformUserServiceImpl implements PlatformUserService {
@Resource
private PlatformUserMapper platformUserMapper;
@Override
public PlatformUserDetails loadUserByUsername(String username) {PlatformUserQuery query = new PlatformUserQuery();
query.setUsername(username);
PlatformUserDTO userDTO = platformUserMapper.getDTOByQuery(query);
if (userDTO == null) {throw new UsernameNotFoundException("Could not find the user'" + username + "'");
}
boolean enabled = UsedEnum.ENABLE.getValue().equals(userDTO.getUsed());
// 设置用户领有的角色
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(platformUserMapper.listRoles(userDTO.getOpenid()));
return new PlatformUserDetails(userDTO, enabled, true, true,
true, grantedAuthorities);
}
}
CustomResponse.java
package com.leven.platform.auth.security.custom;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.leven.commons.core.web.bean.OuterResult;
import com.leven.commons.model.exception.BasicEcode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义响应 JSON
* @author Leven
*/
@Slf4j
public class CustomResponse {
private static final String CONTENT_TYPE = "application/json;charset=UTF-8";
private CustomResponse(){}
/**
* 胜利返回后果
* @param response
* @param obj
* @throws IOException
*/
public static void success(HttpServletResponse response, Object obj) {print(response, BasicEcode.SUCCESS, null, obj);
}
/**
* 异样返回后果
* @param response
* @param ecode
* @throws IOException
*/
public static void error(HttpServletResponse response, String ecode) {error(response, ecode, BasicEcode.getMsg(ecode));
}
/**
* 异样返回后果
* @param response
* @param ecode
* @throws IOException
*/
public static void error(HttpServletResponse response, String ecode, String msg) {print(response, ecode, msg, null);
}
/**
* 异样返回后果
* @param response
* @param ecode
* @throws IOException
*/
public static void error(HttpServletResponse response, String ecode, Object... args) {print(response, ecode, null, null, args);
}
private static void print(HttpServletResponse response, String ecode, String msg, Object data, Object... args) {OuterResult result = OuterResult.newInstance();
result.setEcode(ecode);
if (StringUtils.isBlank(msg)) {msg = BasicEcode.getMsg(ecode);
}
if (args != null && args.length > 0) {msg = String.format(msg, args);
}
result.setMsg(msg);
result.setData(data);
response.setContentType(CONTENT_TYPE);
try {response.getWriter().print(JSON.toJSONString(result, SerializerFeature.WriteMapNullValue));
} catch (IOException e) {log.error("打印返回后果报错:", e);
}
}
}
AuthenticationBean.java
package com.leven.platform.auth.security.custom;
import com.leven.commons.model.pojo.BaseDTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 登录认证 bean
* @author Leven
*/
@ApiModel("登录认证 bean")
@Getter
@Setter
public class AuthenticationBean extends BaseDTO {@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("明码")
private String password;
@ApiModelProperty("验证码")
private String verifyCode;
}
CustomAuthenticationFilter.java
package com.leven.platform.auth.security.custom;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leven.platform.auth.security.WebSecurityConfiguration;
import com.leven.platform.model.constant.PlatformConstant;
import com.leven.platform.model.constant.PlatformExceptionCode;
import com.leven.platform.service.properties.WebProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义认证过滤器
* 为了实现 JSON 形式进行用户登录
*
* @author Leven
*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private WebProperties webProperties;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
// 不是 POST 提交间接报错
if (!PlatformConstant.POST.equalsIgnoreCase(request.getMethod())) {CustomResponse.error(response, PlatformExceptionCode.UNSUPPORTED_REQUEST_METHOD);
return null;
}
String contentType = request.getContentType();
// 不是 JSON 提交间接报错
if (!contentType.equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)
&& !contentType.equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {CustomResponse.error(response, PlatformExceptionCode.UNSUPPORTED_CONTENT_TYPE);
return null;
}
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest;
try (InputStream is = request.getInputStream()) {AuthenticationBean authenticationBean = mapper.readValue(is, AuthenticationBean.class);
String username = authenticationBean.getUsername();
String password = authenticationBean.getPassword();
String verifyCode = authenticationBean.getVerifyCode();
if (StringUtils.isBlank(username)) {CustomResponse.error(response, PlatformExceptionCode.USERNAME_IS_BLANK);
return null;
}
if (StringUtils.isBlank(password)) {CustomResponse.error(response, PlatformExceptionCode.PASSWORD_IS_BLANK);
return null;
}
if (webProperties.getEnableVerifyCode()) {
// 校验图形验证码
HttpSession session = request.getSession();
String sessionCode = (String) session.getAttribute(WebSecurityConfiguration.USER_LOGIN_VERIFY_CODE_KEY);
Long verifyCodeExpired = (Long) session.getAttribute(WebSecurityConfiguration.USER_LOGIN_VERIFY_CODE_EXPIRED_KEY);
if (StringUtils.isBlank(sessionCode) || verifyCodeExpired == null) {CustomResponse.error(response, PlatformExceptionCode.VERIFY_CODE_EXPIRED);
return null;
}
if (System.currentTimeMillis() > verifyCodeExpired) {CustomResponse.error(response, PlatformExceptionCode.VERIFY_CODE_EXPIRED);
return null;
}
if (!sessionCode.equalsIgnoreCase(verifyCode)) {CustomResponse.error(response, PlatformExceptionCode.VERIFY_CODE_ERROR);
return null;
}
// 图形验证码校验胜利后,间接从会话中移除
session.removeAttribute(WebSecurityConfiguration.USER_LOGIN_VERIFY_CODE_KEY);
session.removeAttribute(WebSecurityConfiguration.USER_LOGIN_VERIFY_CODE_EXPIRED_KEY);
}
authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.getUsername(), authenticationBean);
} catch (IOException e) {logger.error("自定义认证出现异常:", e);
CustomResponse.error(response, PlatformExceptionCode.USER_LOGIN_ERROR, e.getMessage());
return null;
}
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
3、配置受权服务
AuthorizationServerConfiguration.java
package com.leven.platform.auth.security;
import com.leven.platform.auth.security.custom.CustomTokenEnhancer;
import com.leven.platform.auth.security.custom.CustomWebResponseExceptionTranslator;
import com.leven.platform.service.PlatformUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
/**
* Oauth2.0 认证受权服务配置
* @author Leven
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private PlatformUserService platformUserService;
/**
* 申明 TokenStore 实现
* @return
*/
@Bean("tokenStore")
public TokenStore tokenStore() {return new JdbcTokenStore(dataSource);
}
/**
* 申明 ClientDetails 实现
* @return
*/
@Bean
public ClientDetailsService clientDetailsService() {return new JdbcClientDetailsService(dataSource);
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {return new JdbcAuthorizationCodeServices(dataSource);
}
@Bean
public TokenEnhancer tokenEnhancer() {return new CustomTokenEnhancer();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetailsService());
}
@SuppressWarnings("unchecked")
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.tokenStore(tokenStore())
.authorizationCodeServices(authorizationCodeServices());
endpoints.tokenEnhancer(tokenEnhancer());
endpoints.userDetailsService(platformUserService);
endpoints.exceptionTranslator(new CustomWebResponseExceptionTranslator());
endpoints.setClientDetailsService(clientDetailsService());
}
}
CustomTokenEnhancer.java
package com.leven.platform.auth.security.custom;
import com.leven.platform.model.pojo.security.PlatformUserDetails;
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 java.util.HashMap;
import java.util.Map;
/**
* 自定义 Token 加强
* @author Leven
*/
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {if (accessToken instanceof DefaultOAuth2AccessToken) {DefaultOAuth2AccessToken token = ((DefaultOAuth2AccessToken) accessToken);
Map<String, Object> additionalInformation = new HashMap<>();
PlatformUserDetails userDetails = (PlatformUserDetails) authentication.getPrincipal();
additionalInformation.put("openid", userDetails.getOpenid());
token.setAdditionalInformation(additionalInformation);
return token;
}
return accessToken;
}
}
CustomWebResponseExceptionTranslator.java
package com.leven.platform.auth.security.custom;
import com.leven.commons.core.web.bean.OuterResult;
import com.leven.commons.model.exception.BasicEcode;
import com.leven.platform.model.constant.PlatformExceptionCode;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
/**
* 自定义异样解决
* @author Leven
*/
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
@Override
public ResponseEntity translate(Exception e) {if (e instanceof OAuth2Exception) {OAuth2Exception exception = ((OAuth2Exception) e);
String code = exception.getOAuth2ErrorCode();
return ResponseEntity.ok(new OuterResult(code, exception.getMessage()));
}
return ResponseEntity.ok(new OuterResult(PlatformExceptionCode.OAUTH2_ERROR,
BasicEcode.getMsg(PlatformExceptionCode.OAUTH2_ERROR)));
}
}
4、配置资源服务
ResourceServerConfiguration.java
package com.leven.platform.auth.security;
import com.leven.platform.auth.security.custom.CustomAccessDeniedHandler;
import com.leven.platform.auth.security.custom.CustomOAuth2AuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* Oauth2.0 资源服务配置
* @author Leven
*/
@Configuration
@EnableResourceServer
@Order(6)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private CustomOAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint;
@Autowired
private CustomAccessDeniedHandler handler;
@Override
public void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/oauth/**").authenticated();}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {resources.authenticationEntryPoint(oAuth2AuthenticationEntryPoint).accessDeniedHandler(handler);
}
}
CustomOAuth2AuthenticationEntryPoint.java
package com.leven.platform.auth.security.custom;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义受权认证异样解决
* @author Leven
*/
@Component
public class CustomOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {CustomResponse.error(response, HttpStatus.UNAUTHORIZED.getReasonPhrase(), e.getMessage());
}
}
CustomAccessDeniedHandler.java
package com.leven.platform.auth.security.custom;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义权限异样解决
* @author Leven
*/
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {CustomResponse.error(response, HttpStatus.FORBIDDEN.getReasonPhrase(), e.getMessage());
}
}
三、SDK 实现
为了不便客户端疾速对接,简略地实现了一个 sdk 包,上面贴出一些要害代码。
1、Maven 援用
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
2、配置局部(config)
在接入服务端前,须要在服务端新建一个利用,失去利用 ID、利用密钥和音讯加密密钥信息。
这里提供一个配置接口和内存实现,SDK 的服务接口须要用到。
接口类 PlatformConfigStorage.java
package com.leven.platform.api.config;
/**
* 配置存储接口
* @author Leven
*/
public interface PlatformConfigStorage {
/**
* 获取平台地址
* @return
*/
String getServerUrl();
/**
* 设置平台地址
* @param serverUrl
*/
void setServerUrl(String serverUrl);
/**
* 获取利用 ID
* @return
*/
String getClientId();
/**
* 设置利用 ID
* @param clientId
*/
void setClientId(String clientId);
/**
* 获取利用密钥
* @return
*/
String getClientSecret();
/**
* 设置利用密钥
* @param clientSecret
*/
void setClientSecret(String clientSecret);
/**
* 获取音讯加密密钥
* @return
*/
String getEncodingAESKey();
/**
* 设置音讯加密密钥
* @param encodingAESKey
*/
void setEncodingAESKey(String encodingAESKey);
/**
* 获取利用受权回调地址
* @return
*/
String getOauth2RedirectUri();
/**
* 设置利用受权回调地址
* @param oauth2redirectUri
*/
void setOauth2RedirectUri(String oauth2redirectUri);
}
内存实现类 MemoryConfigStorage.java
package com.leven.platform.api.config;
/**
* 配置存储内存实现
* @author Leven
*/
public class MemoryConfigStorage implements PlatformConfigStorage {
/** 平台地址 */
private String serverUrl;
/** 利用 ID*/
private String clientId;
/** 利用密钥 */
private String clientSecret;
/** 音讯加密密钥 */
private String encodingAESKey;
/** 利用受权回调地址 */
private String oauth2RedirectUri;
/**
* 获取平台地址
* @return
*/
@Override
public String getServerUrl() {return this.serverUrl;}
/**
* 设置平台地址
* @param serverUrl
*/
@Override
public void setServerUrl(String serverUrl) {this.serverUrl = serverUrl;}
/**
* 获取利用 ID
* @return
*/
@Override
public String getClientId() {return this.clientId;}
/**
* 设置利用 ID
* @param clientId
*/
@Override
public void setClientId(String clientId) {this.clientId = clientId;}
/**
* 获取利用密钥
* @return
*/
@Override
public String getClientSecret() {return this.clientSecret;}
/**
* 设置利用密钥
* @param clientSecret
*/
@Override
public void setClientSecret(String clientSecret) {this.clientSecret = clientSecret;}
/**
* 获取音讯加密密钥
* @return
*/
@Override
public String getEncodingAESKey() {return this.encodingAESKey;}
/**
* 设置音讯加密密钥
* @param encodingAESKey
*/
@Override
public void setEncodingAESKey(String encodingAESKey) {this.encodingAESKey = encodingAESKey;}
/**
* 获取利用受权回调地址
* @return
*/
@Override
public String getOauth2RedirectUri() {return oauth2RedirectUri;}
/**
* 设置利用受权回调地址
* @param oauth2redirectUri
*/
@Override
public void setOauth2RedirectUri(String oauth2redirectUri) {this.oauth2RedirectUri = oauth2redirectUri;}
}
3、申请响应局部
为了对立解决客户端调用服务端接口,这里设计成三个局部,别离为 Request、Response 和 Client。
Request:封装资源服务接口的申请参数、申请门路和响应类型。
Response:封装服务接口返回数据。
Client:执行 Request 申请,返回后果。
3.1 申请接口
PlatformRequest.java
package com.leven.platform.api;
import java.util.Map;
/**
* 申请接口
* @param <T> 响应类
* @author Leven
* @date 2019-06-05
*/
public interface PlatformRequest <T extends AbstractPlatformResponse> {
/**
* 获取 API 的映射路由。*
* @return API 名称
*/
String getApiMappingName();
/**
* 获取所有的 Key-Value 模式的文本申请参数汇合。其中:* <ul>
* <li>Key: 申请参数名 </li>
* <li>Value: 申请参数值 </li>
* </ul>
*
* @return 文本申请参数汇合
*/
Map<String, String> getTextParams();
/**
* 失去以后 API 的响应后果类型
*
* @return 响应类型
*/
Class<T> getResponseClass();}
3.2 响应形象
AbstractPlatformResponse.java
package com.leven.platform.api;
import com.leven.platform.api.internal.util.StringUtils;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Map;
/**
* 响应形象
* @author Leven
* @date 2019-06-05
*/
@Getter
@Setter
public abstract class AbstractPlatformResponse implements Serializable {
private String ecode;
private String msg;
private String body;
private Map<String, String> params;
public AbstractPlatformResponse() {}
public boolean isSuccess() {return PlatformEcode.SUCCESS.equals(ecode) || StringUtils.isEmpty(ecode);
}
}
3.3 执行器
AbstractPlatformClient
package com.leven.platform.api;
import com.leven.platform.api.internal.parser.json.ObjectJsonParser;
import com.leven.platform.api.internal.util.*;
import com.leven.platform.api.internal.util.codec.Base64;
import java.io.IOException;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
/**
* 客户端执行申请形象
* @author Leven
* @date
*/
public abstract class AbstractPlatformClient implements PlatformClient {
private String serverUrl;
private String clientId;
private String format = PlatformConstants.FORMAT_JSON;
private String charset = PlatformConstants.CHARSET_UTF8;
private int connectTimeout = 3000;
private int readTimeout = 15000;
private static final String PREPARE_TIME = "prepareTime";
private static final String PREPARE_COST_TIME = "prepareCostTime";
private static final String REQUEST_TIME = "requestTime";
private static final String REQUEST_COST_TIME = "requestCostTime";
private static final String POST_COST_TIME = "postCostTime";
static {Security.setProperty("jdk.certpath.disabledAlgorithms", "");
}
public AbstractPlatformClient(String serverUrl, String clientId) {
this.serverUrl = serverUrl;
this.clientId = clientId;
}
@Override
public <T extends AbstractPlatformResponse> T execute(PlatformRequest<T> request) throws PlatformApiException {
PlatformParser<T> parser = null;
if (PlatformConstants.FORMAT_JSON.equals(this.format)) {parser = new ObjectJsonParser<>(request.getResponseClass());
}
return execute(request, parser);
}
private <T extends AbstractPlatformResponse> T execute(PlatformRequest<T> request, PlatformParser<T> parser)
throws PlatformApiException {long beginTime = System.currentTimeMillis();
Map<String, Object> rt = doPost(request);
Map<String, Long> costTimeMap = new HashMap<>();
if (rt.containsKey(PREPARE_TIME)) {costTimeMap.put(PREPARE_COST_TIME, (Long)(rt.get(PREPARE_TIME)) - beginTime);
if (rt.containsKey(REQUEST_TIME)) {costTimeMap.put(REQUEST_COST_TIME, (Long)(rt.get(REQUEST_TIME)) - (Long)(rt.get(PREPARE_TIME)));
}
}
T tRsp;
try {
// 解析返回后果
String responseBody = (String) rt.get("rsp");
tRsp = parser.parse(responseBody);
tRsp.setBody(responseBody);
if (costTimeMap.containsKey(REQUEST_COST_TIME)) {costTimeMap.put(POST_COST_TIME, System.currentTimeMillis() - (Long)(rt.get(REQUEST_TIME)));
}
} catch (PlatformApiException e) {PlatformLogger.logBizError((String) rt.get("rsp"), costTimeMap);
throw new PlatformApiException(e);
}
tRsp.setParams((PlatformHashMap) rt.get("textParams"));
if (!tRsp.isSuccess()) {PlatformLogger.logErrorScene(rt, tRsp, "", costTimeMap);
} else {PlatformLogger.logBizSummary(rt, tRsp, costTimeMap);
}
return tRsp;
}
/**
* 发送 Post 申请
* @param request
* @param <T>
* @return
* @throws PlatformApiException
*/
private <T extends AbstractPlatformResponse> Map<String, Object> doPost(PlatformRequest<T> request) throws PlatformApiException {Map<String, Object> result = new HashMap<>();
RequestParametersHolder requestHolder = getRequestHolder(request);
String url = getRequestUrl(request, requestHolder);
result.put(PREPARE_TIME, System.currentTimeMillis());
String rsp;
try {rsp = WebUtils.doPost(url, requestHolder.getApplicationParams(), requestHolder.getPropertyParams(), charset,
connectTimeout, readTimeout, null, 0);
} catch (IOException e) {throw new PlatformApiException(e);
}
result.put(REQUEST_TIME, System.currentTimeMillis());
result.put("rsp", rsp);
result.put("url", url);
return result;
}
/**
* 获取 POST 申请的 base url
*
* @param request
* @return
* @throws PlatformApiException
*/
private String getRequestUrl(PlatformRequest request, RequestParametersHolder requestHolder) throws PlatformApiException {StringBuilder urlSb = new StringBuilder(serverUrl + request.getApiMappingName());
try {String sysMustQuery = WebUtils.buildQuery(requestHolder.getProtocalMustParams(),
charset);
urlSb.append("?");
urlSb.append(sysMustQuery);
} catch (IOException e) {throw new PlatformApiException(e);
}
return urlSb.toString();}
/**
* 组装接口参数,解决加密、签名逻辑
*
* @param request
* @return
* @throws PlatformApiException
*/
private RequestParametersHolder getRequestHolder(PlatformRequest<?> request) {RequestParametersHolder requestHolder = new RequestParametersHolder();
PlatformHashMap appParams = new PlatformHashMap(request.getTextParams());
requestHolder.setApplicationParams(appParams);
// 设置必填参数
if (StringUtils.isEmpty(charset)) {charset = PlatformConstants.CHARSET_UTF8;}
PlatformHashMap protocalMustParams = new PlatformHashMap();
protocalMustParams.put(PlatformConstants.CHARSET, charset);
requestHolder.setProtocalMustParams(protocalMustParams);
// 设置申请头参数
PlatformHashMap propertyParams = new PlatformHashMap();
String accessToken = appParams.get(PlatformConstants.ACCESS_TOKEN);
if (StringUtils.isEmpty(accessToken)) {// 当 accessToken 为空时,须要设置 Authorization
String auth = clientId +":" + getClientSecret();
// 对其进行加密
byte[] rel = Base64.encodeBase64(auth.getBytes());
String res = new String(rel);
propertyParams.put(PlatformConstants.AUTHORIZATION, "Basic" + res);
}
requestHolder.setPropertyParams(propertyParams);
return requestHolder;
}
public abstract String getClientSecret();}
DefaultPlatformClient.java
package com.leven.platform.api;
/**
* 默认执行器
* @author Leven
* @date 2019-06-05
*/
public class DefaultPlatformClient extends AbstractPlatformClient {
private String clientSecret;
public DefaultPlatformClient(String serverUrl, String clientId, String clientSecret) {super(serverUrl, clientId);
this.clientSecret = clientSecret;
}
@Override
public String getClientSecret() {return clientSecret;}
}
PlatformParser.java
package com.leven.platform.api;
/**
* 响应解释器接口
* @author Leven
* @date 2019-06-05
*/
public interface PlatformParser<T extends AbstractPlatformResponse> {
/**
* 把响应字符串解释成相应的畛域对象。*
* @param rsp 响应字符串
* @return 畛域对象
*/
T parse(String rsp) throws PlatformApiException;
/**
* 获取响应类类型。*/
Class<T> getResponseClass() throws PlatformApiException;}
PlatformApiException.java
package com.leven.platform.api;
/**
* 平台接口异样
* @author Leven
* @date 2019-06-05
*/
public class PlatformApiException extends RuntimeException {
private static final long serialVersionUID = -238091758285157331L;
private String errCode;
private String errMsg;
public PlatformApiException() {}
public PlatformApiException(String message, Throwable cause) {super(message, cause);
this.errCode = "1001";
this.errMsg = message;
}
public PlatformApiException(String message) {super(message);
this.errCode = "1001";
this.errMsg = message;
}
public PlatformApiException(Throwable cause) {super(cause);
}
public PlatformApiException(String errCode, String errMsg) {super(errCode + ":" + errMsg);
this.errCode = errCode;
this.errMsg = errMsg;
}
public String getErrCode() {return this.errCode;}
public String getErrMsg() {return this.errMsg;}
}
3.4 常量类
PlatformConstants.java
package com.leven.platform.api;
public class PlatformConstants {private PlatformConstants() {}
/**
* 利用 ID
*/
public static final String CLIENT_ID = "client_id";
/**
* 申请路由
*/
public static final String MAPPING = "mapping";
/**
* 申请资源必须带上 access_token
*/
public static final String ACCESS_TOKEN = "access_token";
/**
* 受权信息
*/
public static final String AUTHORIZATION = "Authorization";
/**
* 字符集
*/
public static final String CHARSET = "charset";
/**
* 默认工夫格局
**/
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* Date 默认时区
**/
public static final String DATE_TIMEZONE = "GMT+8";
/**
* UTF- 8 字符集
**/
public static final String CHARSET_UTF8 = "UTF-8";
/**
* JSON 格局
*/
public static final String FORMAT_JSON = "json";
/**
* SDK 版本号
*/
public static final String SDK_VERSION = "platform-sdk-1.0.0";
/**
* 申请 IP
*/
public static final String REQUEST_IP = "REQUEST_IP";
/**
* 音讯类型
*/
public static class MsgType {private MsgType() {}
/** 网关验证 */
public static final String CHECK_GATEWAY = "CHECK_GATEWAY";
}
}
PlatformEcode.java
package com.leven.platform.api;
import java.io.Serializable;
/**
* 对立接口响应编码
* @author Leven
* @date 2019-06-05
*/
public class PlatformEcode implements Serializable {public static final String SUCCESS = "1000";}
3.5 接口实现
这里只例举俩个接口,能够依据业务须要增加其余的。
3.5.1 获取 Token 接口
申请类 PlatformOAuthTokenRequest.java
package com.leven.platform.api.request;
import com.leven.platform.api.PlatformRequest;
import com.leven.platform.api.internal.util.PlatformHashMap;
import com.leven.platform.api.response.PlatformOAuthTokenResponse;
import lombok.Getter;
import lombok.Setter;
import java.util.Map;
@Getter
@Setter
public class PlatformOAuthTokenRequest implements PlatformRequest<PlatformOAuthTokenResponse> {
private String grantType;
private String redirectUri;
private String code;
private String refreshToken;
public PlatformOAuthTokenRequest() {}
@Override
public String getApiMappingName() {return "/oauth/token";}
@Override
public Map<String, String> getTextParams() {PlatformHashMap txtParams = new PlatformHashMap();
txtParams.put("grant_type", this.grantType);
txtParams.put("redirect_uri", this.redirectUri);
txtParams.put("code", this.code);
txtParams.put("refresh_token", this.refreshToken);
return txtParams;
}
@Override
public Class<PlatformOAuthTokenResponse> getResponseClass() {return PlatformOAuthTokenResponse.class;}
}
响应类 PlatformOAuthTokenResponse.java
package com.leven.platform.api.response;
import com.leven.platform.api.AbstractPlatformResponse;
import com.leven.platform.api.internal.mapping.ApiField;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
@Setter
public class PlatformOAuthTokenResponse extends AbstractPlatformResponse {@ApiField("access_token")
private String accessToken;
@ApiField("token_type")
private String tokenType;
@ApiField("refresh_token")
private String refreshToken;
@ApiField("expires_in")
private String expiresIn;
@ApiField("scope")
private String scope;
@ApiField("openid")
private String openid;
public PlatformOAuthTokenResponse() {}
}
3.5.2 获取用户信息接口
申请类 PlatformOAuthUserinfoRequest.java
package com.leven.platform.api.request;
import com.leven.platform.api.PlatformRequest;
import com.leven.platform.api.internal.util.PlatformHashMap;
import com.leven.platform.api.response.PlatformOAuthUserinfoResponse;
import lombok.Getter;
import lombok.Setter;
import java.util.Map;
@Getter
@Setter
public class PlatformOAuthUserinfoRequest implements PlatformRequest<PlatformOAuthUserinfoResponse> {
private String accessToken;
private String openid;
@Override
public String getApiMappingName() {return "/oauth/userinfo";}
@Override
public Map<String, String> getTextParams() {PlatformHashMap txtParams = new PlatformHashMap();
txtParams.put("access_token", this.accessToken);
txtParams.put("openid", this.openid);
return txtParams;
}
@Override
public Class<PlatformOAuthUserinfoResponse> getResponseClass() {return PlatformOAuthUserinfoResponse.class;}
}
响应类 PlatformOAuthUserinfoResponse.java
package com.leven.platform.api.response;
import com.leven.platform.api.AbstractPlatformResponse;
import com.leven.platform.api.internal.mapping.ApiField;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
@Setter
public class PlatformOAuthUserinfoResponse extends AbstractPlatformResponse {@ApiField("openid")
private String openid;
@ApiField("username")
private String username;
@ApiField("trueName")
private String trueName;
@ApiField("phone")
private String phone;
@ApiField("idNumber")
private String idNumber;
public PlatformOAuthUserinfoResponse() {}
}
4、音讯推送局部
这里我写死了应用 JSON 格局,其实还能够设计得更灵便。
4.1 音讯接管
PlatformJSONMsg.java
package com.leven.platform.api.message;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
/**
* JSON 音讯接管
* @author Leven
* @date 2019-06-12
*/
@Getter
@Setter
public class PlatformJSONMsg implements Serializable {
private String type;
private Long timestamp;
private String content;
private String sign;
@Override
public int hashCode() {return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj) {return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public String toString() {return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
4.2 音讯回复
PlatformJSONOutMsg.java
package com.leven.platform.api.message;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
/**
* JSON 音讯回复
* @author Leven
* @date 2019-06-12
*/
@Getter
@Setter
public class PlatformJSONOutMsg implements Serializable {
private String ecode = "1000";
private String msg = "胜利";
private Object data;
private PlatformJSONOutMsg() {}
private PlatformJSONOutMsg(Object data) {this.data = data;}
private PlatformJSONOutMsg(String msg) {
this.ecode = "1001";
this.msg = msg;
}
public static PlatformJSONOutMsg success() {return new PlatformJSONOutMsg();
}
public static PlatformJSONOutMsg success(Object data) {return new PlatformJSONOutMsg(data);
}
public static PlatformJSONOutMsg fail(String errMsg) {return new PlatformJSONOutMsg(errMsg);
}
@Override
public int hashCode() {return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj) {return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public String toString() {return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
4.3 音讯处理器
对立接口类 PlatformMsgHandler.java
package com.leven.platform.api.message.handler;
import com.leven.platform.api.PlatformApiException;
import com.leven.platform.api.message.PlatformJSONMsg;
import com.leven.platform.api.message.PlatformJSONOutMsg;
import com.leven.platform.api.service.PlatformService;
/**
* 音讯处理器接口
* @author Leven
* @date 2019-07-09
*/
public interface PlatformMsgHandler {
/**
* 音讯解决
* @param msg json 音讯
* @param platformService 平台服务接口
* @return 音讯回复
* @throws PlatformApiException
*/
PlatformJSONOutMsg handle(PlatformJSONMsg msg, PlatformService platformService) throws PlatformApiException;
}
网关验证解决 CheckGatewayHandler.java
package com.leven.platform.api.message.handler;
import com.leven.platform.api.PlatformApiException;
import com.leven.platform.api.message.PlatformJSONMsg;
import com.leven.platform.api.message.PlatformJSONOutMsg;
import com.leven.platform.api.message.model.CheckGatewayDTO;
import com.leven.platform.api.service.PlatformService;
/**
* 网关验证音讯处理器
* @author Leven
* @date 2019-07-09
*/
public class CheckGatewayHandler implements PlatformMsgHandler {
@Override
public PlatformJSONOutMsg handle(PlatformJSONMsg msg, PlatformService platformService) throws PlatformApiException {CheckGatewayDTO dto = platformService.parseMsg(msg, CheckGatewayDTO.class);
return PlatformJSONOutMsg.success(dto.getEchoStr());
}
}
4.4 音讯内容对象
目前只有一种音讯,能够依据业务须要增加。
4.4.1 网关验证音讯
CheckGatewayDTO.java
package com.leven.platform.api.message.model;
import lombok.Getter;
import lombok.Setter;
/**
* 网关验证
* @author Leven
* @date 2019-07-09
*/
@Getter
@Setter
public class CheckGatewayDTO extends BaseDTO {
/**
* 随机字符串
*/
private String echoStr;
}
5、服务接口局部
5.1 受权服务
PlatformOAuth2Service.java
package com.leven.platform.api.service;
import com.leven.platform.api.PlatformApiException;
import com.leven.platform.api.response.PlatformOAuthTokenResponse;
import com.leven.platform.api.response.PlatformOAuthUserinfoResponse;
/**
* 凋谢受权服务接口
* @author Leven
* @date 2019-07-09
*/
public interface PlatformOAuth2Service {
/**
* 获取受权链接
* @param serverUrl 平台地址
* @param clientId 利用 ID
* @param redirectUri 利用受权回调
* @return 受权链接
* @throws PlatformApiException
*/
String getAuthorizeUri(String serverUrl, String clientId, String redirectUri) throws PlatformApiException;
/**
* 通过 code 换取 access_token
* @param code 受权码
* @return token 对象
* @throws PlatformApiException
*/
PlatformOAuthTokenResponse getAccessToken(String code) throws PlatformApiException;
/**
* 刷新 access_token
* @param refreshToken 刷新 token
* @return token 对象
* @throws PlatformApiException
*/
PlatformOAuthTokenResponse refreshAccessToken(String refreshToken) throws PlatformApiException;
/**
* 获取用户信息
* @param accessToken 受权 token
* @param openid 用户 ID
* @return 用户信息
* @throws PlatformApiException
*/
PlatformOAuthUserinfoResponse getUserinfo(String accessToken, String openid) throws PlatformApiException;
}
PlatformOAuth2ServiceImpl.java
package com.leven.platform.api.service.impl;
import com.leven.platform.api.DefaultPlatformClient;
import com.leven.platform.api.PlatformApiException;
import com.leven.platform.api.PlatformClient;
import com.leven.platform.api.PlatformConstants;
import com.leven.platform.api.config.PlatformConfigStorage;
import com.leven.platform.api.request.PlatformOAuthTokenRequest;
import com.leven.platform.api.request.PlatformOAuthUserinfoRequest;
import com.leven.platform.api.response.PlatformOAuthTokenResponse;
import com.leven.platform.api.response.PlatformOAuthUserinfoResponse;
import com.leven.platform.api.service.PlatformOAuth2Service;
import com.leven.platform.api.service.PlatformService;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* 凋谢受权服务接口
* @author Leven
* @date 2019-07-09
*/
public class PlatformOAuth2ServiceImpl implements PlatformOAuth2Service {
/** 受权 url*/
private static final String AUTHORIZE_URI = "SERVER_URL/oauth/authorize?client_id=CLIENT_ID&response_type=code" +
"&scope=read&redirect_uri=REDIRECT_URI";
private PlatformService platformService;
private PlatformClient platformClient;
public PlatformClient getPlatformClient() {PlatformConfigStorage configStorage = platformService.getConfigStorage();
if (platformClient == null) {platformClient = new DefaultPlatformClient(configStorage.getServerUrl(),
configStorage.getClientId(),
configStorage.getClientSecret());
}
return platformClient;
}
public PlatformOAuth2ServiceImpl(PlatformService platformService) {this.platformService = platformService;}
/**
* 获取受权链接
* @param serverUrl 平台地址
* @param clientId 利用 ID
* @param redirectUri 利用受权回调
* @return 受权链接
* @throws PlatformApiException
*/
@Override
public String getAuthorizeUri(String serverUrl, String clientId, String redirectUri) throws PlatformApiException {
try {return AUTHORIZE_URI.replace("SERVER_URL", serverUrl)
.replace("CLIENT_ID", clientId)
.replace("REDIRECT_URI", URLEncoder.encode(redirectUri, PlatformConstants.CHARSET_UTF8));
} catch (UnsupportedEncodingException e) {throw new PlatformApiException("拼接通行证网页受权 url 出现异常:", e);
}
}
@Override
public PlatformOAuthTokenResponse getAccessToken(String code) throws PlatformApiException {String oauth2RedirectUri = platformService.getConfigStorage().getOauth2RedirectUri();
PlatformOAuthTokenRequest request = new PlatformOAuthTokenRequest();
request.setGrantType("authorization_code");
request.setRedirectUri(oauth2RedirectUri);
request.setCode(code);
PlatformOAuthTokenResponse response = getPlatformClient().execute(request);
if (!response.isSuccess()) {throw new PlatformApiException(String.format("调用平台接口获取 access_token 失败:%s", response.getMsg()));
}
return response;
}
/**
* 刷新 access_token
* @param refreshToken 刷新 token
* @return token 对象
* @throws PlatformApiException
*/
@Override
public PlatformOAuthTokenResponse refreshAccessToken(String refreshToken) throws PlatformApiException {PlatformOAuthTokenRequest request = new PlatformOAuthTokenRequest();
request.setGrantType("refresh_token");
request.setRefreshToken(refreshToken);
PlatformOAuthTokenResponse response = getPlatformClient().execute(request);
if (!response.isSuccess()) {throw new PlatformApiException(String.format("调用平台接口刷新 access_token 失败:%s", response.getMsg()));
}
return response;
}
/**
* 获取通行证信息
* @param accessToken 受权 token
* @param openid 通行证 ID
* @return 通行证信息
* @throws PlatformApiException
*/
@Override
public PlatformOAuthUserinfoResponse getUserinfo(String accessToken, String openid) throws PlatformApiException {PlatformOAuthUserinfoRequest request = new PlatformOAuthUserinfoRequest();
request.setAccessToken(accessToken);
request.setOpenid(openid);
PlatformOAuthUserinfoResponse response = getPlatformClient().execute(request);
if (!response.isSuccess()) {throw new PlatformApiException(String.format("调用平台接口获取通行证信息失败:%s", response.getMsg()));
}
return response;
}
}
5.2 平台服务
PlatformService.java
package com.leven.platform.api.service;
import com.leven.platform.api.PlatformApiException;
import com.leven.platform.api.config.PlatformConfigStorage;
import com.leven.platform.api.message.PlatformJSONMsg;
import javax.servlet.http.HttpServletRequest;
/**
* 平台服务接口
* @author Leven
* @date 2019-07-09
*/
public interface PlatformService {
/**
* 获取配置存储
* @return
*/
PlatformConfigStorage getConfigStorage();
/**
* 设置配置存储
* @param configProvider
*/
void setConfigStorage(PlatformConfigStorage configProvider);
/**
* 音讯验证
* @param msg
* @throws PlatformApiException
*/
void checkMsg(PlatformJSONMsg msg) throws PlatformApiException;
/**
* 解析音讯
* @param msg
* @param clazz
* @param <T>
* @return
* @throws PlatformApiException
*/
<T> T parseMsg(PlatformJSONMsg msg, Class<T> clazz) throws PlatformApiException;
/**
* 获取凋谢受权服务接口
* @return
*/
PlatformOAuth2Service getOAuth2Service();
/**
* 从 request 中读取 json 音讯
* @param req
* @return
* @throws PlatformApiException
*/
PlatformJSONMsg getJSONMsg(HttpServletRequest req) throws PlatformApiException;
}
AbstractPlatformServiceImpl.java
package com.leven.platform.api.service.impl;
import com.leven.platform.api.PlatformApiException;
import com.leven.platform.api.config.PlatformConfigStorage;
import com.leven.platform.api.internal.util.PlatformUtils;
import com.leven.platform.api.internal.util.SHA1SignUtils;
import com.leven.platform.api.internal.util.StringUtils;
import com.leven.platform.api.message.PlatformJSONMsg;
import com.leven.platform.api.service.PlatformOAuth2Service;
import com.leven.platform.api.service.PlatformService;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 平台服务接口形象实现
* @author Leven
* @date 2019-07-09
*/
public class AbstractPlatformServiceImpl implements PlatformService {
protected PlatformConfigStorage configStorage;
private PlatformOAuth2Service oAuth2Service = new PlatformOAuth2ServiceImpl(this);
public AbstractPlatformServiceImpl() {}
/**
* 获取配置存储
* @return
*/
@Override
public PlatformConfigStorage getConfigStorage() {return this.configStorage;}
/**
* 设置配置存储
* @param configProvider
*/
@Override
public void setConfigStorage(PlatformConfigStorage configProvider) {this.configStorage = configProvider;}
/**
* 音讯验证
* @param msg 平台 json 音讯
* @throws PlatformApiException
*/
@Override
public void checkMsg(PlatformJSONMsg msg) throws PlatformApiException {if (msg == null) {throw new PlatformApiException("平台音讯对象为空!");
}
String type = msg.getType();
Long timestamp = msg.getTimestamp();
String content = msg.getContent();
String sign = msg.getSign();
if (StringUtils.isEmpty(type)) {throw new PlatformApiException("音讯类型为空!");
}
if (timestamp == null) {throw new PlatformApiException("工夫戳为空!");
}
if (StringUtils.isEmpty(content)) {throw new PlatformApiException("音讯内容为空!");
}
if (StringUtils.isEmpty(sign)) {throw new PlatformApiException("签名为空!");
}
checkSignature(msg, configStorage.getEncodingAESKey());
}
/**
* 解析音讯
* @param msg 平台 json 音讯
* @param clazz 音讯类
* @param <T> 音讯对象
* @return 音讯对象
* @throws PlatformApiException
*/
@Override
public <T> T parseMsg(PlatformJSONMsg msg, Class<T> clazz) throws PlatformApiException {return PlatformUtils.parseMsg(msg, configStorage.getEncodingAESKey(), clazz);
}
/**
* 获取凋谢受权服务接口
* @return
*/
@Override
public PlatformOAuth2Service getOAuth2Service() {return this.oAuth2Service;}
/**
* 从 request 中读取 json 音讯
* @param req
* @return
* @throws PlatformApiException
*/
@SuppressWarnings("unchecked")
@Override
public PlatformJSONMsg getJSONMsg(HttpServletRequest req) throws PlatformApiException {
try {
BufferedReader br = null;
br = new BufferedReader(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8));
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {sb.append(line);
}
Map<String, Object> map = (Map<String, Object>) PlatformUtils.parseJson(sb.toString());
return PlatformUtils.mapToBean(map, PlatformJSONMsg.class);
} catch (Exception e) {throw new PlatformApiException("解析平台发送的音讯出现异常:", e);
}
}
/**
* 验证签名
* @param msg 平台 json 音讯
* @param encodingAESKey 音讯加密密钥
* @throws PlatformApiException
*/
private void checkSignature(PlatformJSONMsg msg, String encodingAESKey) throws PlatformApiException {Map<String, String> paramMap = new HashMap<>(4);
paramMap.put("type", msg.getType());
paramMap.put("timestamp", String.valueOf(msg.getTimestamp()));
paramMap.put("content", msg.getContent());
String sign = SHA1SignUtils.sign(encodingAESKey, paramMap);
if (!sign.equalsIgnoreCase(msg.getSign())) {throw new PlatformApiException("签名谬误!");
}
}
}
DefaultPlatformServiceImpl.java
package com.leven.platform.api.service.impl;
/**
* 平台服务接口默认实现
* @author Leven
* @date 2019-07-09
*/
public class DefaultPlatformServiceImpl extends AbstractPlatformServiceImpl {
}
四、应用 SDK
这里以 SpringBoot 我的项目为例,当然其余框架也是能够的。
1、引入 jar
<dependency>
<groupId>com.leven</groupId>
<artifactId>platform-sdk</artifactId>
<version>${platform.version}</version>
</dependency>
2、增加配置
application.yml
# 平台配置
platform:
# 服务地址
server-url: http://192.168.199.2:8008/platform
# 利用 ID
client-id: p9r4i6zbj6jt81e62
# 利用密钥
client-secret: 868gpxep0kpjf19f2hq9xritbd4sno1x
# 音讯加密密钥
encoding-AES-key: TfTaPJCAU54YcnucQAYeFm26Htwf2qs0
# 利用受权回调地址
oauth2-redirect-uri: http://192.168.199.2:8088/platform/redirect
# 前端登录地址(前后端拆散我的项目)front-login-uri: http://192.168.199.2:8088/#/platform/login
PlatformProperties.java
package com.leven.visual.service.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 平台配置
*/
@Component
@ConfigurationProperties(prefix = "platform")
@Data
public class PlatformProperties {
/**
* 服务地址
*/
private String serverUrl;
/**
* 利用 ID
*/
private String clientId;
/**
* 利用 ID
*/
private String clientSecret;
/**
* 音讯加密密钥
*/
private String encodingAESKey;
/**
* 利用受权回调地址
*/
private String oauth2RedirectUri;
/**
* 前端登录地址
*/
private String frontLoginUri;
}
3、注入平台服务
/**
* 注入平台服务接口
* @return
*/
@Bean
public PlatformService platformService() {PlatformService platformService = new DefaultPlatformServiceImpl();
PlatformConfigStorage configStorage = new MemoryConfigStorage();
configStorage.setServerUrl(platformProperties.getServerUrl());
configStorage.setClientId(platformProperties.getClientId());
configStorage.setClientSecret(platformProperties.getClientSecret());
configStorage.setEncodingAESKey(platformProperties.getEncodingAESKey());
configStorage.setOauth2RedirectUri(platformProperties.getOauth2RedirectUri());
platformService.setConfigStorage(configStorage);
return platformService;
}
4、controller 实现
PlatformController.java
package com.leven.visual.web.controller;
import com.leven.commons.model.exception.BasicEcode;
import com.leven.commons.model.exception.SPIException;
import com.leven.platform.api.PlatformApiException;
import com.leven.platform.api.PlatformConstants;
import com.leven.platform.api.message.PlatformJSONMsg;
import com.leven.platform.api.message.PlatformJSONOutMsg;
import com.leven.platform.api.message.handler.CheckGatewayHandler;
import com.leven.platform.api.message.router.PlatformMsgRouter;
import com.leven.platform.api.response.PlatformOAuthTokenResponse;
import com.leven.platform.api.response.PlatformOAuthUserinfoResponse;
import com.leven.platform.api.service.PlatformOAuth2Service;
import com.leven.platform.api.service.PlatformService;
import com.leven.visual.service.properties.PlatformProperties;
import com.leven.visual.service.properties.WebProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 接入平台
* @author Leven
* @date 2019-07-02
*/
@Slf4j
@Controller
@RequestMapping("platform")
public class PlatformController {
/**
* 平台配置
*/
@Autowired
private PlatformProperties platformProperties;
/**
* web 配置
*/
@Autowired
private WebProperties webProperties;
/**
* 平台服务接口
*/
@Autowired
private PlatformService platformService;
/**
* 利用网关
*
* @param jsonMsg 平台 json 音讯
* @return 音讯响应后果
*/
@ResponseBody
@PostMapping("gateway")
public PlatformJSONOutMsg gateway(@RequestBody PlatformJSONMsg jsonMsg) {
try {
// 音讯验证
platformService.checkMsg(jsonMsg);
// 创立音讯路由,开始解决音讯
PlatformMsgRouter router = new PlatformMsgRouter(platformService);
// 设置路由匹配规定和音讯处理器
router.rule().msgType(PlatformConstants.MsgType.CHECK_GATEWAY)
.handler(new CheckGatewayHandler()).end();
// 音讯解决胜利,返回后果
return router.route(jsonMsg);
} catch (PlatformApiException e) {log.error("利用网关解决出错,调用平台 API 产生异样:", e);
return PlatformJSONOutMsg.fail(e.getErrMsg());
}
}
/**
* 利用受权回调
*
* @param code 受权码
* @param req servlet 申请
* @param res servlet 响应
*/
@GetMapping("redirect")
public void redirect(String code, HttpServletRequest req, HttpServletResponse res) {HttpSession session = req.getSession();
// 前端登录地址(前后端拆散我的项目)
String redirect = platformProperties.getFrontLoginUri();
try {PlatformOAuth2Service oAuth2Service = platformService.getOAuth2Service();
// 通过 code 获取 access_token
PlatformOAuthTokenResponse tokenResponse = oAuth2Service.getAccessToken(code);
// 通过 access_token 获取通行证信息
PlatformOAuthUserinfoResponse userinfoResponse = oAuth2Service.getUserinfo(tokenResponse.getAccessToken(),
tokenResponse.getOpenid());
// 登录用户名
String username = userinfoResponse.getUsername();
// 设置 session
...
} catch (PlatformApiException e) {log.error("利用受权回调解决出错,调用平台 API 产生异样:", e);
// 出现异常,跳转到前端谬误页面
redirect = webProperties.getErrorUrl();} catch (SPIException e) {log.error("利用受权回调解决出错,调用业务接口产生异样:", e);
// 出现异常,跳转到前端谬误页面
redirect = webProperties.getErrorUrl();}
try {
// 执行跳转
res.sendRedirect(redirect);
} catch (IOException e) {throw new SPIException(BasicEcode.ILLEGAL_PARAMETER);
}
}
}
五、后续
本文为思否原创文章,未经容许不得转载。
如读者发现有谬误,欢送留言!