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
授权,但是这样的实现方式有两个问题:
- 脱离了
OAuth2
的管理 - 不灵活:例如系统使用 密码模式 授权,网页版需要增加图形验证码校验,但是手机端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
的解析
- 每种
grant_type
都会有一个对应的TokenGranter
实现类。 - 所有
TokenGranter
实现类都通过CompositeTokenGranter
中的tokenGranters
集合存起来。 - 然后通过判断
grantType
参数来定位具体使用那个TokenGranter
实现类来处理授权。
第二部分:关于授权登录逻辑
- 每种
授权方式
都会有一个对应的AuthenticationProvider
实现类来实现。 - 所有
AuthenticationProvider
实现类都通过ProviderManager
中的providers
集合存起来。 TokenGranter
类会 new 一个AuthenticationToken
实现类,如UsernamePasswordAuthenticationToken
传给ProviderManager
类。- 而
ProviderManager
则通过AuthenticationToken
来判断具体使用那个AuthenticationProvider
实现类来处理授权。 - 具体的登录逻辑由
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中默认开启 } }