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的获取,最后要做的是把这个自定义授权模式类,放入系统默认的授权模式集合中,这样在CompositeTokenGrantergrant方法中,才能循环匹配到我们自定义的授权模式,进而直接获取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  阅读(4684)  评论(0编辑  收藏  举报