这一篇是完善oauth服务,实现登录功能,获取到access_token.主要用password模式
获取流程:
url带上认证参数经过网关,网关转发到oauth服务,oauth带上参数然后调用user服务获取用户信息,然后在oauth里面校验用户名密码是否正确,最后返回access_token。
详情请看:
《Spring Security Oauth2 认证流程(password模式)》
改造oauth微服务开始
oauth微服务添加jar
<!--oauth-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
这里最主要的是oauth的配置类
SecurityConfiguration.java
package com.study.vcloud.oauth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 这一步的配置是必不可少的,否则SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
/**
* @Date 14:35 2019/7/11
* @Param [不用加密]
* @return org.springframework.security.crypto.password.PasswordEncoder
**/
@Bean
public static PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
}
OAuth2ServerConfig.java
package com.study.vcloud.oauth.config;
import com.study.vcloud.oauth.bean.UserVoDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
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.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
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.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
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 java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
@Configuration
@Order(Integer.MIN_VALUE)
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
private static final String DEMO_RESOURCE_ID = "vcloud";
private static final String SCOPE = "scope";
private static final String CLIENT_ID = "client_id";
private static final String CLIENT_SECRET = "client_secret";
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
}
}
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置客户端
clients.inMemory().withClient(CLIENT_ID)
.resourceIds(DEMO_RESOURCE_ID)
.authorizedGrantTypes("password", "refresh_token")
.scopes(SCOPE)
.authorities("oauth2")
.secret(CLIENT_SECRET);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints
.tokenEnhancer(tokenEnhancerChain)
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
// 2018-4-3 增加配置,允许 GET、POST 请求获取 token,即访问端点:oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
endpoints.reuseRefreshTokens(true);
//oauth2登录异常处理
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.allowFormAuthenticationForClients()
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
/**
* @Author Pan Weilong
* @Description jwt加密秘钥
* @Date 17:58 2019/7/10
**/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(DEMO_RESOURCE_ID);
return converter;
}
/**
* jwt 生成token 定制化处理
* @return TokenEnhancer
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
UserVoDetail userDto = (UserVoDetail) authentication.getUserAuthentication().getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>(1);
additionalInfo.put("license", DEMO_RESOURCE_ID);
additionalInfo.put("userId" , userDto.getUserId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
//设置token的过期时间30分钟
Calendar nowTime = Calendar.getInstance();
nowTime.add(Calendar.MINUTE, 30);
((DefaultOAuth2AccessToken) accessToken).setExpiration(nowTime.getTime());
return accessToken;
};
}
}
}
然后是存放用户信息的bean
package com.study.vcloud.oauth.bean;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author Pan Weilong
* @date 2019/8/14 17:40
* @description: 接口.
*/
public class UserVoDetail implements UserDetails {
private Long userId;
private String username;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
然后重写认证方法
package com.study.vcloud.oauth.service;
import com.study.vcloud.oauth.bean.UserVoDetail;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
/**
* @author Pan Weilong
* @date 2019/7/9 15:57
* @description: 接口.
*/
@Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserVoDetail userVoDetail = new UserVoDetail();
userVoDetail.setUserId(1L);
userVoDetail.setUsername("hello");
userVoDetail.setPassword("hello");
return userVoDetail;
}
}
上面的认证先写死用户名和密码,实际上调用user服务从数据库获取的,这里先写死
现在有用户名和密码了,我们来获取token
为了简单还是不启动网关,直接调用这个服务,启动eureka和oauth服务
测试:
请求token
这里面的参数一个都不能少,对应的值你可以自定义,刚刚我在配置类上自定义了
POST方式:访问
127.0.0.1:1027/oauth/token?username=hello&password=hello&grant_type=password&scope=scope&client_id=client_id&client_secret=client_secret
获取成功。下一篇改造获取用户这块,直接调用user服务然后获取用户信息