1

授权码模式使用是最广泛,而且大多数互联网站点都使用了此模式,如第三方使用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);
        }
    }
}

五、后续

本文为思否原创文章,未经允许不得转载。
如读者发现有错误,欢迎留言!


Leven
10 声望1 粉丝

德以明理、学以精工