springSecurity + OAuth2 获取Token流程分析以及增加协议授权模式

springSecurity + OAuth2 获取Token流程分析以及增加协议授权模式

 

 

一、什么是OAuth2协议?

OAuth 2.0 是一个关于授权的开放的网络协议,是目前最流行的授权机制。

数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

由于授权的场景众多,OAuth 2.0 协议定义了获取令牌的四种授权方式,分别是:

  • 授权码模式:授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。
  • 简化模式:简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
  • 密码模式:密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。
  • 客户端模式:客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

四种授权模式分别使用不同的 grant_type 来区分

二、为什么要自定义授权类型?

虽然 OAuth2 协议定义了4种标准的授权模式,但是在实际开发过程中还是远远满足不了各种变态的业务场景,需要我们去扩展。

例如增加图形验证码、手机验证码、手机号密码登录等等的场景

而常见的做法都是通过增加 过滤器Filter 的方式来扩展 Spring Security 授权,但是这样的实现方式有两个问题:

  1. 脱离了 OAuth2 的管理
  2. 不灵活:例如系统使用 密码模式 授权,网页版需要增加图形验证码校验,但是手机端APP又不需要的情况下,使用增加 Filter 的方式去实现就比较麻烦了。

所以目前在 Spring Security 中比较优雅和灵活的扩展方式就是通过自定义 grant_type 来增加授权模式。

三、实现思路

在扩展之前首先需要先了解 Spring Security 的整个授权流程,我以 密码模式 为例去展开分析,如下图所示

 

3.1. 流程分析

整个授权流程关键点分为以下两个部分:

我们先看一下获取token的运行流程:

(1)在发起 URL+/oauth/token 获取token的请求后,实际上是请求 TokenEndpoint 类的postAccessToken或者getAccessToken方法,就相当于一个普通的concoller请求方法,根据请求类型是get或者post,其实get请求内部也是调用post请求的方法)

 

 (2)在postAccessToken这个方法中,在这个方法的132行调用TokenGranter类的grant方法来获取token,这个方法也是最重要的,通过这个方法我们可以对请求的参数进行校验是否合法,是否给予令牌。

 

(3)TokenGranter是一个接口,它有多个实现类,CompositeTokenGranter是其中之一,在grant方法中,会循环遍历所有的授权方式,根据请求参数携带的授权方式码,来匹配对应的授权处理实现类,调用实现类中的grant方法。那么关键点来了,请求参数中携带的是我们自定义的授权方式码,如果要匹配上,那么首先我们要创建自定义的授权处理类,然后把这个授权处理类放入Spring Security默认的授权处理集合中,这样才能循环匹配上,进行下一步。 

4)创建自定义授权处理类,我们可以继承TokenGranter来实现自定义的身份验证以便获取token,而AbstractTokenGranter是一个继承TokenGranter的实现类,一般我们都会继承这个类进行使用。

从下面代码可以看出,这个抽象类的grant方法返回token,在最后调用了getOAuth2Authentication方法,所以

我们只需要继承AbstractTokenGranter类然后重写getOAuth2Authentication方法就可以了。

(5)对于参数的校验,在上图中36行代码,authenticationManager.authenticate(userAuth);这个方法中进行校验的,下面我们先看一下源码,再去实现自定义的认证提供商类AuthenticationProvider。

        在源码中,AuthenticationProvider是一个接口,里面有两个方法,一个是校验参数的方法,另一个则是根据当前认证信息匹配出对应的认证提供商类,这个接口有很多实现类,其中ProviderManager类是非常关键的,在这个类中的参数校验方法中,会根据当前要认证的对象,获取符合要求的所有的认证提供商,然后循环匹配出对应的认证提供商,在调取校验方法进行参数校验

 

 

 (6)由上图可知,我们只需要实现ProviderManager接口的两个方法,自定义自己的参数校验方法,并且把这个自定义的ProviderManager加入到认证提供商集合中,在循环匹配的时候即可匹配到我们自定义的ProviderManager,进行参数校验。

 

 (7)把自定义的ProviderManager放入ProviderManager集合中,我的方法如下,在配置文件中,重写configure方法,配置ProviderManager,这里除了配置我们自定义的ProviderManager之外,还需要额外配置默认的密码授权模式的ProviderManager,否则client认证将不会通过。

 

 

 

 (8)经过以上步骤,已经实现自定义token的获取,最后要做的是把这个自定义授权模式类,放入系统默认的授权模式集合中,这样在CompositeTokenGranter的grant方法中,才能循环匹配到我们自定义的授权模式,进而直接获取token。我们先来看一下,系统默认的授权模式集合是在哪里初始化的?答案在AuthorizationServerEndpointsConfigurer这个类中559行,调用了getDefaultTokenGranters()方法,并且创建了 CompositeTokenGranter的实例对象,进行初始化。查看源码可以发现,系统已经把默认的授权模式全都写死在程序里了,因此我的解决思路是如下的

把AuthorizationServerEndpointsConfigurer中,初始化默认授权方式的代码复制一下,在配置文件中额外重新配置自定义的模式,代码部分截图(完整参考最后)如下

 

 

 

 (9)授权认证服务端点配置

 

 

 

 (10)最后:查看源码可以发现,当前clientid拥有的授权方式码是通过ClientDetails client = clientDetailsService.loadClientByClientId(clientId);获取的,在数据库中配置的,因此我们需要再oauth_client_details表中,在对应的 clientid 的  authorized_grant_types 字段中加上自定义的授权模式码。

 

第一部分:关于授权类型 grant_type 的解析

  1. 每种 grant_type 都会有一个对应的 TokenGranter 实现类。
  2. 所有 TokenGranter 实现类都通过 CompositeTokenGranter 中的 tokenGranters 集合存起来。
  3. 然后通过判断 grantType 参数来定位具体使用那个 TokenGranter 实现类来处理授权。

第二部分:关于授权登录逻辑

  1. 每种 授权方式 都会有一个对应的 AuthenticationProvider 实现类来实现。
  2. 所有 AuthenticationProvider 实现类都通过 ProviderManager 中的 providers 集合存起来。
  3. TokenGranter 类会 new 一个 AuthenticationToken 实现类,如 UsernamePasswordAuthenticationToken 传给 ProviderManager 类。
  4. 而 ProviderManager 则通过 AuthenticationToken 来判断具体使用那个 AuthenticationProvider 实现类来处理授权。
  5. 具体的登录逻辑由 AuthenticationProvider 实现类来实现,如 DaoAuthenticationProvider

 

完整代码:

OpenIdTokenGranter
package com.lpw.CustomerSecurity;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author dw
 * @ClassName OpenIdTokenGranter
 * @Description
 * @Date 2020/9/15 13:19
 * @Version 1.0
 */
public class OpenIdTokenGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "openId";

    private final AuthenticationManager authenticationManager;

    public OpenIdTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
            , ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
        Authentication userAuth = new OpenIdAuthenticationToken(parameters);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        userAuth = authenticationManager.authenticate(userAuth);
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate openId: " + parameters);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

}
package com.lpw.CustomerSecurity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Author dw
 * @ClassName TokenGranterConfig
 * @Description token授权模式配置类
 * @Date 2020/9/15 13:26
 * @Version 1.0
 */
@Configuration
public class TokenGranterConfig {

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private OpenIdUserDetailService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenStore tokenStore;

    private AuthorizationCodeServices authorizationCodeServices;

    private boolean reuseRefreshToken = true;

    private AuthorizationServerTokenServices tokenServices;

    private TokenGranter tokenGranter;

    /**
     * 授权模式
     *
     * @return
     */
    @Bean
    public TokenGranter tokenGranter() {
        if (tokenGranter == null) {
            tokenGranter = new TokenGranter() {
                private CompositeTokenGranter delegate;

                @Override
                public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                    if (delegate == null) {
                        delegate = new CompositeTokenGranter(getDefaultTokenGranters());
                    }
                    return delegate.grant(grantType, tokenRequest);
                }
            };
        }
        return tokenGranter;
    }

    /**
     * 程序支持的授权类型
     *
     * @return
     */
    private List<TokenGranter> getDefaultTokenGranters() {
        AuthorizationServerTokenServices tokenServices = tokenServices();
        AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
        OAuth2RequestFactory requestFactory = requestFactory();

        List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
        // 添加授权码模式
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
        // 添加刷新令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加隐式授权模式
        tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加客户端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加自定义Openid授权模式
        tokenGranters.add(new OpenIdTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        if (authenticationManager != null) {
            // 添加密码模式
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        }
        return tokenGranters;
    }

    /**
     * TokenServices
     *
     * @return
     */
    private AuthorizationServerTokenServices tokenServices() {
        if (tokenServices != null) {
            return tokenServices;
        }
        this.tokenServices = createDefaultTokenServices();
        return tokenServices;
    }

    /**
     * 授权码API
     *
     * @return
     */
    private AuthorizationCodeServices authorizationCodeServices() {
        if (authorizationCodeServices == null) {
            authorizationCodeServices = new InMemoryAuthorizationCodeServices();
        }
        return authorizationCodeServices;
    }

    /**
     * OAuth2RequestFactory的默认实现,它初始化参数映射中的字段,
     * 验证授权类型(grant_type)和范围(scope),并使用客户端的默认值填充范围(scope)(如果缺少这些值)。
     *
     * @return
     */
    private OAuth2RequestFactory requestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }

    /**
     * 默认 TokenService
     *
     * @return
     */
    private DefaultTokenServices createDefaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore);
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(reuseRefreshToken);
        tokenServices.setClientDetailsService(clientDetailsService);
        addUserDetailsService(tokenServices, this.userDetailsService);
        return tokenServices;
    }

    /**
     * 添加预身份验证
     *
     * @param tokenServices
     * @param userDetailsService
     */
    private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
        if (userDetailsService != null) {
            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService));
            tokenServices.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider>asList(provider)));
        }
    }
}
package com.lpw.CustomerSecurity;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
 * @Author dw
 * @ClassName OpenIdAuthenticationToken
 * @Description
 * @Date 2020/9/15 13:09
 * @Version 1.0
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;

    public OpenIdAuthenticationToken(Object principal) {
        super(null);
        this.principal = principal;
        setAuthenticated(false);
    }

    public OpenIdAuthenticationToken(Object principal,
                                     Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }


    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}
package com.lpw.CustomerSecurity;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Map;

/**
 * @Author dw
 * @ClassName OpenIdAuthenticationProvider
 * @Description
 * @Date 2020/9/15 13:14
 * @Version 1.0
 */
public class OpenIdAuthenticationProvider implements AuthenticationProvider {

    private CustomUserDetailsService customUserDetailsService;


    @Override
    public Authentication authenticate(Authentication authentication) {
        OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;
        Map<String, String> principal = (Map<String, String>) authenticationToken.getPrincipal();
        UserDetails userDetails = customUserDetailsService.loadUserByUsername(principal);
        if (userDetails == null) {
            throw new InternalAuthenticationServiceException("openId错误");
        }
        OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(userDetails, userDetails.getAuthorities());
        // 设置前端传过来的具体参数
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public CustomUserDetailsService getCustomUserDetailsService() {
        return customUserDetailsService;
    }

    public void setCustomUserDetailsService(CustomUserDetailsService customUserDetailsService) {
        this.customUserDetailsService = customUserDetailsService;
    }




}
package com.lpw.CustomerSecurity;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lpw.Enum.WeChatUserParamEnum;
import com.lpw.dao.*;
import com.lpw.entity.*;
import com.lpw.utils.DateUtil;
import com.lpw.utils.WeChatUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;
import java.util.Objects;

/**
 * @Author dw
 * @ClassName OpenIdUserDetailService
 * @Description 微信小程序授权,注册,获取用户信息
 * @Date 2020/9/15 17:03
 * @Version 1.0
 */
@Service
public class OpenIdUserDetailService implements CustomUserDetailsService {

    @Autowired
    private IExhibitionDao exhibitionDao;
    @Autowired
    private IUserMapperDao userMapperDao;
    @Autowired
    private IVisitorExhibitionDao visitorExhibitionDao;
    @Autowired
    private IVisitorDao visitorDao;
    @Autowired
    private IVisitorUserDao visitorUserDao;


    @Override
    public UserDetails loadUserByUsername(String userName, String userType) throws UsernameNotFoundException {
        return null;
    }

    @Override
    public UserDetails loadUserByUsername(Map<String, String> principal) throws UsernameNotFoundException {
        // 获取微信用户授权的信息
        User weChatUserInfo = getWeChatUserInfo(principal);
        String exhibitionId = principal.get(WeChatUserParamEnum.EXHIBITION_ID.getValue());
        // 检查用户信息
        userIsRegistered(weChatUserInfo, exhibitionId);
        // 查询用户
        VisitorUser visitorUser = visitorUserDao.queryVisitorUser(weChatUserInfo.getLoginName());
        return visitorUser;
    }

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        return null;
    }

    /**
     * 获取微信用户信息
     */
    public User getWeChatUserInfo(Map<String, String> principal) {
        String code = principal.get(WeChatUserParamEnum.CODE.getValue());
        String exhibitionId = principal.get(WeChatUserParamEnum.EXHIBITION_ID.getValue());
        String rawData = principal.get(WeChatUserParamEnum.RAW_DATA.getValue());
//        String encrypteData = principal.get(WeChatUserParamEnum.ENCRYPTE_DATA.getValue());
//        String iv = principal.get(WeChatUserParamEnum.IV.getValue());
        String signature = principal.get(WeChatUserParamEnum.SIGNATURE.getValue());
        // 查询当前小程序的配置信息
        Exhibition exhibition = exhibitionDao.queryExhibitionWechatConf(Integer.valueOf(exhibitionId));
        // 2.开发者服务器 登录凭证校验接口 appId + appSecret + 接收小程序发送的code
        WeChatUtil weChatUtil = new WeChatUtil(exhibition);
        JSONObject SessionKeyOpenId = weChatUtil.getSessionKeyOrOpenId(code);
        // 3.接收微信接口服务 获取返回的参数
        String openid = SessionKeyOpenId.get("openid", String.class);
        String sessionKey = SessionKeyOpenId.get("session_key", String.class);
        // 用户非敏感信息:rawData
        // 签名:signature
        JSONObject rawDataJson = JSONUtil.parseObj(rawData);
        // 4.校验签名 小程序发送的签名signature与服务器端生成的签名signature2 = sha1(rawData + sessionKey)
        String signature2 = DigestUtils.sha1Hex(rawData + sessionKey);
        if (!signature.equals(signature2)) {
            throw new UsernameNotFoundException("签名不匹配");
        }
        //encrypteData比rowData多了appid和openid
//        JSONObject userInfo = weChatUtil.getUserInfo(encrypteData,
//                sessionKey, iv);
        User user = new User();
        user.setLoginName(openid);
        user.setName(rawDataJson.get("nickName", String.class));
        user.setIcon(rawDataJson.get("avatarUrl", String.class));
//        0:女      1: 男
        Integer sex = rawDataJson.get("gender", String.class).equals("女") ? 0 : 1;
        user.setSex(sex);
        return user;
    }

    /**
     * 查询当前访客是否买需要注册
     */
    @Transactional(rollbackForClassName = "RuntimeException")
    public void userIsRegistered(User weChatUserInfo, String exhibitionId){
        // 查询当前用户是否存在
        MyUser myUser = userMapperDao.loadUserByUsername(weChatUserInfo.getLoginName());
        Integer userId = null;
        if (Objects.isNull(myUser)) {
            // 新增用户
            weChatUserInfo.setCreateTime(DateUtil.currentDateTime());
             userMapperDao.insert(weChatUserInfo);
            userId = weChatUserInfo.getUserId();
        }else {
            userId = myUser.getUserId();
        }
        // 查询当前访客是否存在
        QueryWrapper<Visitor> visitorQueryWrapper = new QueryWrapper<>();
        visitorQueryWrapper.lambda().eq(Visitor::getUserId, userId);
        Visitor hasVisitor = visitorDao.selectOne(visitorQueryWrapper);
        Integer visitorId = null;
        if(Objects.isNull(hasVisitor)){
            // 插入访客到访客表
            Visitor visitor = new Visitor();
            visitor.setUserId(userId);
            visitor.setCreateTime(DateUtil.currentDateTime());
            visitorDao.insert(visitor);
            visitorId = visitor.getVisitorId();
        }else {
            visitorId = hasVisitor.getVisitorId();
        }
        // 查询当前访客是否注册到了当前展会
        QueryWrapper<VisitorExhibition> query = new QueryWrapper<>();
        query.lambda().eq(VisitorExhibition::getExhibitionId, exhibitionId);
        query.lambda().eq(VisitorExhibition::getVisitorId, visitorId);
        VisitorExhibition visitorExhibition = visitorExhibitionDao.selectOne(query);
        if (Objects.isNull(visitorExhibition)) {
            // 当前访客未注册当前展会,新增访客、展会关系
            VisitorExhibition visitorExhibition1 = new VisitorExhibition();
            visitorExhibition1.setExhibitionId(Integer.valueOf(exhibitionId));
            visitorExhibition1.setVisitorId(visitorId);
            visitorExhibition1.setOpenId(weChatUserInfo.getLoginName());
            visitorExhibitionDao.insert(visitorExhibition1);
        }
    }




}
package com.lpw.CustomerSecurity;


import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Map;

/**
 * @Author dw
 * @ClassName CustomUserDetailsService
 * @Description 继承原来的UserDetailsService新增自定义方法
 * @Date 2020/8/19 17:23
 * @Version 1.0
 */
public interface CustomUserDetailsService extends UserDetailsService {

    /**
     * 根据用户名、用户类型查询用户
     * @param userName  用户名
     * @param userType 用户类型
     * @return
     * @throws UsernameNotFoundException
     */
    UserDetails loadUserByUsername(String userName, String userType) throws UsernameNotFoundException;


    /**
     * 根据OpenId、 展会id查询用户
     * @param principal 前端传过来的微信小程序授权登录的参数信息
     * @return
     * @throws UsernameNotFoundException
     */
    UserDetails loadUserByUsername(Map<String, String> principal) throws UsernameNotFoundException;



}
package com.lpw.auth;

import com.lpw.CustomerSecurity.UserDetailsServiceImpl;
import com.lpw.security.CustomWebResponseExceptionTranslator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * @Author dw
 * @ClassName AuthorizationServerConfig
 * @Description
 * @Date 2020/8/17 10:46
 * @Version 1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private DataSource dataSource;
    /**
     *  认证管理器,认证用户信息
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 用户认证处理逻辑
     */
    @Autowired
    private UserDetailsServiceImpl myUserDetailService;

    /**
     * 认证异常全局处理
     */
    @Autowired
    private CustomWebResponseExceptionTranslator webResponseExceptionTranslator;


    @Autowired
    private TokenGranter tokenGranter;


    @Bean
    public TokenStore tokenStore() {
        //使用内存中的 token store
//        return new InMemoryTokenStore();
        //使用Jdbctoken store
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }



    /**
     * @return
     * @Author dw
     * @Description 配置token的过期日期等
     * @Date 2020/4/23 18:32
     * @Param
     */
    @Bean
    @Primary
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        // access token有效期
        services.setAccessTokenValiditySeconds(60 * 60 * 24);
        // refresh token有效期
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        // 支持使用refresh token刷新access token
        services.setSupportRefreshToken(true);
        // 允许重复使用refresh token
        services.setReuseRefreshToken(true);
        services.setTokenStore(tokenStore());
        return services;
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端, 用于client认证
        clients.withClientDetails(clientDetails());
// 第一次使用的时候,需要配置客户端信息,或者手动添加客户端信息到数据库oauth_client_details表中
//        clients.jdbc(dataSource)
//                .withClient("myClient")
//                .secret(new BCryptPasswordEncoder().encode("123456"))
//                .authorizedGrantTypes("password", "refresh_token")//允许授权范围
////                .authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限
//                .scopes("all")
//                .accessTokenValiditySeconds(7200)
//                .refreshTokenValiditySeconds(7200);
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                .tokenServices(defaultTokenServices())
                .authenticationManager(authenticationManager)
                .userDetailsService(myUserDetailService)
                .reuseRefreshTokens(true)
                //接收GET和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                //四种授权模式+刷新令牌的模式+自定义授权模式
                .tokenGranter(tokenGranter)
                .exceptionTranslator(webResponseExceptionTranslator);

    }



    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                //允许表单登录
                .allowFormAuthenticationForClients();
    }


    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }


}
package com.lpw.security;

import com.lpw.CustomerSecurity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @Author dw
 * @ClassName WebSecurityConfig
 * @Description
 * @Date 2020/8/19 13:53
 * @Version 1.0
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private OpenIdUserDetailService openIdUserDetailService;

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean(name = "CustomerAuthenticationProvider")
    public AuthenticationProvider customAuthenticationProvider() {
        CustomerAuthenticationProvider customAuthenticationProvider = new CustomerAuthenticationProvider();
        customAuthenticationProvider.setUserDetailsService(userDetailsService);
        customAuthenticationProvider.setHideUserNotFoundExceptions(false);
        customAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return customAuthenticationProvider;
    }

    @Bean(name = "openIdAuthenticationProvider")
    public AuthenticationProvider openIdAuthenticationProvider() {
        OpenIdAuthenticationProvider customAuthenticationProvider = new OpenIdAuthenticationProvider();
        customAuthenticationProvider.setCustomUserDetailsService(openIdUserDetailService);
        return customAuthenticationProvider;
    }

    @Bean(name = "daoAuthenticationProvider")
    public AuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }


    /**
     * 自定义的用户认证
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(customAuthenticationProvider())
                .authenticationProvider(daoAuthenticationProvider())
                .authenticationProvider(openIdAuthenticationProvider());
    }


    /**
     * 用来配置拦截保护的请求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //定义哪些url需要被保护  哪些不需要保护
                .authorizeRequests()
                //定义这两个链接不需要登录可访问
                .antMatchers("/oauth/token", "oauth/check_token", "/v2/api-docs", "/swagger-resources/configuration/ui",
                        "/swagger-resources", "/swagger-resources/configuration/security",
                        "/swagger-ui.html", "/course/coursebase/**", "/webjars/**").permitAll()
                //定义所有的都不需要登录  目前是测试需要
//             .antMatchers("/**").permitAll()
                .anyRequest().authenticated() //其他的都需要登录
                //.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色
                .and()
                .formLogin()
//                .loginPage("/login") //如果未登录则跳转登录的页面   这儿可以控制登录成功和登录失败跳转的页面
                .and()
                .csrf().disable();//防止跨站请求  spring security中默认开启
    }

}

 

posted @ 2020-09-15 19:26  邓维-java  阅读(5331)  评论(0)    收藏  举报