3、JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。首先我们使用keytool生成RSA证书gitegg.jks,复制到gitegg-oauth工程的resource目录下,CMD命令行进入到JDK安装目录的bin目录下, 使用keytool命令生成gitegg.jks证书
keytool -genkey -alias gitegg -keyalg RSA -keystore gitegg.jks4、新建GitEggUserDetailsServiceImpl.java实现SpringSecurity获取用户信息接口,用于SpringSecurity鉴权时获取用户信息
package com.gitegg.oauth.service; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.gitegg.oauth.enums.AuthEnum; import com.gitegg.platform.base.constant.AuthConstant; import com.gitegg.platform.base.domain.GitEggUser; import com.gitegg.platform.base.enums.ResultCodeEnum; import com.gitegg.platform.base.result.Result; import com.gitegg.service.system.api.feign.IUserFeign; import cn.hutool.core.bean.BeanUtil; import lombok.RequiredArgsConstructor; /** * 实现SpringSecurity获取用户信息接口 * * @author gitegg */ @Service @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class GitEggUserDetailsServiceImpl implements UserDetailsService { private final IUserFeign userFeign; private final HttpServletRequest request; @Override public GitEggUserDetails loadUserByUsername(String username) { // 获取登录类型,密码,二维码,验证码 String authLoginType = request.getParameter(AuthConstant.AUTH_TYPE); // 获取客户端id String clientId = request.getParameter(AuthConstant.AUTH_CLIENT_ID); // 远程调用返回数据 Result<object> result; // 通过手机号码登录 if (!StringUtils.isEmpty(authLoginType) && AuthEnum.PHONE.code.equals(authLoginType)) { String phone = request.getParameter(AuthConstant.PHONE_NUMBER); result = userFeign.queryUserByPhone(phone); } // 通过账号密码登录 else if(!StringUtils.isEmpty(authLoginType) && AuthEnum.QR.code.equals(authLoginType)) { result = userFeign.queryUserByAccount(username); } else { result = userFeign.queryUserByAccount(username); } // 判断返回信息 if (null != result && result.isSuccess()) { GitEggUser gitEggUser = new GitEggUser(); BeanUtil.copyProperties(result.getData(), gitEggUser, false); if (gitEggUser == null || gitEggUser.getId() == null) { throw new UsernameNotFoundException(ResultCodeEnum.INVALID_USERNAME.msg); } if (CollectionUtils.isEmpty(gitEggUser.getRoleIdList())) { throw new UserDeniedAuthorizationException(ResultCodeEnum.INVALID_ROLE.msg); } return new GitEggUserDetails(gitEggUser.getId(), gitEggUser.getTenantId(), gitEggUser.getOauthId(), gitEggUser.getNickname(), gitEggUser.getRealName(), gitEggUser.getOrganizationId(), gitEggUser.getOrganizationName(), gitEggUser.getOrganizationIds(), gitEggUser.getOrganizationNames(), gitEggUser.getRoleId(), gitEggUser.getRoleIds(), gitEggUser.getRoleName(), gitEggUser.getRoleNames(), gitEggUser.getRoleIdList(), gitEggUser.getRoleKeyList(), gitEggUser.getResourceKeyList(), gitEggUser.getDataPermission(), gitEggUser.getAvatar(), gitEggUser.getAccount(), gitEggUser.getPassword(), true, true, true, true, AuthorityUtils.createAuthorityList(gitEggUser.getRoleIdList().toArray(new String[gitEggUser.getRoleIdList().size()]))); } else { throw new UsernameNotFoundException(result.getMsg()); } } }5、新建AuthorizationServerConfig.java用于认证服务相关配置,正式环境请一定记得修改gitegg.jks配置的密码,这里默认为123456。TokenEnhancer 为登录用户的扩展信息,可以自己定义。
package com.gitegg.oauth.config; import java.security.KeyPair; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 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.TokenGranter; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; import com.anji.captcha.service.CaptchaService; import com.gitegg.oauth.granter.GitEggTokenGranter; import com.gitegg.oauth.service.GitEggClientDetailsServiceImpl; import com.gitegg.oauth.service.GitEggUserDetails; import com.gitegg.platform.base.constant.AuthConstant; import com.gitegg.platform.base.constant.TokenConstant; import com.gitegg.service.system.api.feign.IUserFeign; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; /** * 认证服务配置 */ @Configuration @EnableAuthorizationServer @RequiredArgsConstructor(onConstructor_ = @Autowired) public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private final DataSource dataSource; private final AuthenticationManager authenticationManager; private final UserDetailsService userDetailsService; private final IUserFeign userFeign; private final RedisTemplate redisTemplate; private final CaptchaService captchaService; @Value("${captcha.type}") private String captchaType; /** * 客户端信息配置 */ @Override @SneakyThrows public void configure(ClientDetailsServiceConfigurer clients) { GitEggClientDetailsServiceImpl jdbcClientDetailsService = new GitEggClientDetailsServiceImpl(dataSource); jdbcClientDetailsService.setFindClientDetailsSql(AuthConstant.FIND_CLIENT_DETAILS_SQL); jdbcClientDetailsService.setSelectClientDetailsSql(AuthConstant.SELECT_CLIENT_DETAILS_SQL); clients.withClientDetails(jdbcClientDetailsService); } /** * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services) */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); List<tokenenhancer> tokenEnhancers = new ArrayList<>(); tokenEnhancers.add(tokenEnhancer()); tokenEnhancers.add(jwtAccessTokenConverter()); tokenEnhancerChain.setTokenEnhancers(tokenEnhancers); // 获取自定义tokenGranter TokenGranter tokenGranter = GitEggTokenGranter.getTokenGranter(authenticationManager, endpoints, redisTemplate, userFeign, captchaService, captchaType); endpoints.authenticationManager(authenticationManager) .accessTokenConverter(jwtAccessTokenConverter()) .tokenEnhancer(tokenEnhancerChain) .userDetailsService(userDetailsService) .tokenGranter(tokenGranter) /** * * refresh_token有两种使用方式:重复使用(true)、非重复使用(false),默认为true * 1.重复使用:access_token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准 * 2.非重复使用:access_token过期刷新时, refresh_token过期时间延续,在refresh_token有效期内刷新而无需失效再次登录 */ .reuseRefreshTokens(false); } /** * 允许表单认证 */ @Override public void configure(AuthorizationServerSecurityConfigurer security) { security.allowFormAuthenticationForClients() .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } /** * 使用非对称加密算法对token签名 */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setKeyPair(keyPair()); return converter; } /** * 从classpath下的密钥库中获取密钥对(公钥+私钥) */ @Bean public KeyPair keyPair() { KeyStoreKeyFactory factory = new KeyStoreKeyFactory( new ClassPathResource("gitegg.jks"), "123456".toCharArray()); KeyPair keyPair = factory.getKeyPair( "gitegg", "123456".toCharArray()); return keyPair; } /** * JWT内容增强 */ @Bean public TokenEnhancer tokenEnhancer() { return (accessToken, authentication) -> { Map<string, object=""> map = new HashMap<>(2); GitEggUserDetails user = (GitEggUserDetails) authentication.getUserAuthentication().getPrincipal(); map.put(TokenConstant.TENANT_ID, user.getTenantId()); map.put(TokenConstant.OAUTH_ID, user.getOauthId()); map.put(TokenConstant.USER_ID, user.getId()); map.put(TokenConstant.ORGANIZATION_ID, user.getOrganizationId()); map.put(TokenConstant.ORGANIZATION_NAME, user.getOrganizationName()); map.put(TokenConstant.ORGANIZATION_IDS, user.getOrganizationIds()); map.put(TokenConstant.ORGANIZATION_NAMES, user.getOrganizationNames()); map.put(TokenConstant.ROLE_ID, user.getRoleId()); map.put(TokenConstant.ROLE_NAME, user.getRoleName()); map.put(TokenConstant.ROLE_IDS, user.getRoleIds()); map.put(TokenConstant.ROLE_NAMES, user.getRoleNames()); map.put(TokenConstant.ACCOUNT, user.getAccount()); map.put(TokenConstant.REAL_NAME, user.getRealName()); map.put(TokenConstant.NICK_NAME, user.getNickname()); map.put(TokenConstant.ROLE_ID_LIST, user.getRoleIdList()); map.put(TokenConstant.ROLE_KEY_LIST, user.getRoleKeyList()); //不把权限菜单放到jwt里面,当菜单太多时,会导致jwt长度不可控 // map.put(TokenConstant.RESOURCE_KEY_LIST, user.getResourceKeyList()); map.put(TokenConstant.DATA_PERMISSION, user.getDataPermission()); map.put(TokenConstant.AVATAR, user.getAvatar()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map); return accessToken; }; } }