oauth2授权认证----授权码模式
直接上代码
搭建授权服务器
授权服务器的作用就是用来生成token,下面的代码主要是用来生成token,配置客户端信息,配置token生成方式
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private MyBatisClientDetailsService myBatisClientDetailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private DataSource dataSource;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Autowired
private UserDetailsServiceImpl userDetailsService;
/**
* 1.用来配置客户端信息,即请求令牌的一方,需要颁发客户端标识或客户端秘钥。
* 查询数据库获取,配置好之后会自动查询客户端信息
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
System.out.println("第三步:配置客户端管理,根据client_id查询");
clients.withClientDetails(myBatisClientDetailsService);
}
/**
* 2.客户端要来配置令牌,需要配置申请的访问端点(即要访问的URL)和令牌服务(包括令牌如何生成,如何存储。。。)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)//密码模式必须要配置认证管理器
.authorizationCodeServices(authorizationCodeServices())//授权码必须要配
.userDetailsService(userDetailsService)
.tokenServices(tokenService())//令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
/**
* 3.配置令牌端点的安全约束,即什么情况下可以访问该端点
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
System.out.println("第四步:发放令牌 isAuthenticated()");
security.tokenKeyAccess("permitAll()") //允许所有人请求令牌
.checkTokenAccess("permitAll()") //已验证的客户端才能请求check_token端点
.allowFormAuthenticationForClients();
}
//令牌管理服务
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(myBatisClientDetailsService);//客户端详情服务
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);
service.setReuseRefreshToken(false);//刷新token的同时刷新refresh_token
// service.setTokenEnhancer(accessTokenConverter);
//令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
service.setAccessTokenValiditySeconds(600); // token有效期
service.setRefreshTokenValiditySeconds(1200); // refresh_token有效期
return service;
}
//授权码模式专用对象
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
}
配置token相关信息,包括秘钥,使用jwt生成token,并且token增强(就是把相关用户信息放到token中)。
@Configuration
public class TokenConfig {
private final String SIGNING_KEY = "123456";
@Autowired
private ZWUserAuthenticationConverter zwUserAuthenticationConverter;
@Bean
public TokenStore tokenStore() {
//JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
/**
* 重写增强token的方法
* 自定义返回相应的信息
*
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
// 与登录时候放进去的UserDetail实现类一致
ZWUser userInfo = (ZWUser) authentication.getUserAuthentication().getPrincipal();
/* 自定义一些token属性 ***/
final Map<String, Object> additionalInformation = new HashMap<>(18);
Long userId = userInfo.getUser_id();
additionalInformation.put("name", name);
additionalInformation.put("userId", userId);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
return super.enhance(accessToken, authentication);
}
};
accessTokenConverter.setSigningKey(SIGNING_KEY);
((DefaultAccessTokenConverter) accessTokenConverter.getAccessTokenConverter()).setUserTokenConverter(zwUserAuthenticationConverter);
return accessTokenConverter;
}
}
用户实体类,要继承security中的user类,用户信息可以通过security拿到,放到token中
/**
* 扩展用户信息
* 认证后转换的用户信息
*
* @author zhuowen
* @since 2019/6/21
*/
public class ZWUser extends User {
/**
* 用户ID
*/
@Getter
private Long user_id;
/**
* 用户姓名
*/
@Getter
private String name;
/**
* Construct the <code>User</code> with the details required by
* {@link DaoAuthenticationProvider}.
*
* @param username the username presented to the
* <code>DaoAuthenticationProvider</code>
* @param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
* @param enabled set to <code>true</code> if the user is enabled
* @param accountNonExpired set to <code>true</code> if the account has not expired
* @param credentialsNonExpired set to <code>true</code> if the credentials have not
* expired
* @param accountNonLocked set to <code>true</code> if the account is not locked
* @param authorities the authorities that should be granted to the caller if they
* presented the correct username and password and the user is enabled. Not null.
* @throws IllegalArgumentException if a <code>null</code> value was passed either as
* a parameter or as an element in the <code>GrantedAuthority</code> collection
*/
public ZWUser( Long user_id, String username, String name, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.user_id = user_id;
this.name = name;
}
}
配置用户信息转换器,主要是为了解决refresh_token后,生成的新token没有用户信息的问题,重写extractAuthentication方法,把用户信息重新返回,刷新refresh_token后,新的token中也会包含用户信息,如果不重写,新的token用户信息会变成用户名
/**
* 用户认证转化器
* /根据 oauth/check_token 的结果转化用户信息
*
* @author zhuowen
* @since 2019/06/21
*/
@Component
public class ZWUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
private static final String USER_ID = "user_id";
private static final String DEPT_ID = "dept_id";
private static final String ROLE_ID = "role_id";
private static final String NAME = "name";
private static final String TENANT_ID = "tenant_id";
private static final String N_A = "N/A";
/**
* Extract information about the user to be used in an access token (i.e. for resource servers).
*
* @param authentication an authentication representing a user
* @return a map of key values representing the unique information about the user
*/
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new LinkedHashMap<>();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
/**
* Inverse of {@link #convertUserAuthentication(Authentication)}. Extracts an Authentication from a map.
*
* @param map a map of user information
* @return an Authentication representing the user or null if there is none
*/
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
String username = (String) map.get(USERNAME);
String name = (String) map.get(NAME);
Integer userId = (Integer) map.get("userId");
ZWUser zwUser = new ZWUser(userId.longValue(),username,name,"N/A",true,true,true,true,authorities);
// String userStr = (String)map.get("userInfo");
// System.out.println("userObj=========="+userStr);
// ObjectMapper objectMapper = new ObjectMapper();
// ZWUser user = objectMapper.convertValue(userObj, ZWUser.class);
// System.out.println("user=========="+user.toString());
// System.out.println(jsonObject.toJSONString());
// SysUser user = JSON.parseObject(jsonObject.toJSONString(), SysUser.class);
return new UsernamePasswordAuthenticationToken(zwUser, N_A, authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
授权类就完成了,主要是设置token生成方式,使用jwt方式存储,配置token增强,从数据库查询客户端信息,重写extractAuthentication方法
接下来我们写资源管理类,说的通俗点就是生成token后,每次访问接口都需要验证token,网关只是进行一些过滤(也有把token验证放在网关的),并不会验证token,我的验证token的方式写在各个微服务中,如果token验证通过了,就可以访问接口
搭建资源服务器
资源服务器比较简单,把生成token的方式移到资源服务器就OK了,生成token的方式我们用jwt,解析当然也用jwt,所以注入TokenStore ,tokenStore(tokenStore)就是用jwt的方式解析token。
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources//该客户端拥有的资源id
// .tokenServices(tokenService()) //验证客户端的令牌
// .authenticationEntryPoint(new LLGAuthenticationEntryPoint())
.tokenStore(tokenStore)
.stateless(true);
System.out.println("资源管理:令牌验证2");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/get/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
token配置类,生成token的秘钥要和解析token的秘钥一样
@Configuration
public class TokenConfig {
private final String SIGNING_KEY = "12342";
@Autowired
private ZWUserAuthenticationConverter zwUserAuthenticationConverter;
@Bean
public TokenStore tokenStore() {
//JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
/**
* 重写增强token的方法
* 自定义返回相应的信息
*
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration}
ZWUser userInfo = (ZWUser) authentication.getUserAuthentication().getPrincipal();
/* 自定义一些token属性 ***/
final Map<String, Object> additionalInformation = new HashMap<>(18);
Long userId = userInfo.getUser_id();
additionalInformation.put("userId", userId);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
return super.enhance(accessToken, authentication);
}
};
accessTokenConverter.setSigningKey(SIGNING_KEY);
((DefaultAccessTokenConverter) accessTokenConverter.getAccessTokenConverter()).setUserTokenConverter(zwUserAuthenticationConverter);
return accessTokenConverter;
}
}
这样资源服务器也搭建完成,我的是授权和资源是放在两个不同的服务,也可以放在同一个服务下,看你们自己怎么设计了。