关于springboot:Spring-Security-OAuth2-之授权码模式

52次阅读

共计 60306 个字符,预计需要花费 151 分钟才能阅读完成。

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

五、后续

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

正文完
 0