密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。
在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。 客户端发出的HTTP请求,包含以下参数:
grant_type:表示授权类型,此处的值固定为"password",必选项。
username:表示用户名,必选项。
password:表示用户的密码,必选项。
scope:表示权限范围,可选项。
(C)认证服务器确认无误后,向客户端提供访问令牌。
redis 存储模式
pom
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
spring-boot 整合security -->
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
<exclusions>
<exclusion>
<artifactId>spring-security-oauth2artifactId>
<groupId>org.springframework.security.oauthgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.security.oauthgroupId>
<artifactId>spring-security-oauth2artifactId>
<version>2.3.3.RELEASEversion>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.6.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
dependencies>
security配置 配置登陆权限
@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll()
.and().authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest().authenticated()
.and().logout().permitAll()
.and().csrf().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
service 配置用户名密码
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123456");
return new User("admin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
redis配置中心
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
}
授权认证
/**
* 认证授权Server端
*/
@Component
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManagerBean;
@Autowired
private UserService userService;
@Autowired
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
.reuseRefreshTokens(false)
.tokenStore(tokenStore)
.userDetailsService(userService) //refresh_token是否重复使用
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); //支持GET,POST请求
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单提交
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
/**
* appid mayikt secret= 123456
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/**
* 密码模式获取 token http://localhost:8080/oauth/token?username=admin&password=123456&grant_type=password&client_id=appId&client_secret=123456&scope=all
*
* 刷新密码 http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=appId&client_secret=123456&refresh_token=1b46f93f-af95-4ce6-afae-618eca676ebc
*/
clients.inMemory()
// appid 表里取 这里写死
.withClient("appId")
// 密钥 表里取 这里写死
.secret(passwordEncoder.encode("123456"))
// 授权码
.authorizedGrantTypes("authorization_code","password","client_credentials","refresh_token")
// 作用域
.scopes("all")
// 资源的id 表里取 这里写死
.resourceIds("mayikt_resource")
// 回调地址 表里取 这里写死
.redirectUris("http://www.mayikt.com/callback");
}
}
contoller 认证token
@RestController
@RequestMapping("/oauth")
public class AuthorizationContoller {
@Autowired
private RestTemplate restTemplate;
/**
* 校验token
*
* @param token
* @return
*/
@GetMapping("/checkToken")
public String getCheckToken(String token){
String url = "http://localhost:8080/oauth/check_token?token="+token;
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
}
yml
spring:
redis:
database: 0
host: 127.0.0.1
server:
port: 8080
密码模式获取 token http://localhost:8080/oauth/token?username=admin&password=123456&grant_type=password&client_id=appId&client_secret=123456&scope=all
refresh_token用来刷新token,刷新后原来的token立即失效
验证token
http://localhost:8080/oauth/checkToken?token=6dd755e7-ab40-4e77-a4ae-60ebba57aeaf
刷新token
刷新后原来的token就会失效
http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=appId&client_secret=123456&refresh_token=1b46f93f-af95-4ce6-afae-618eca676ebc
<!‐‐JWT依赖‐‐>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
<!‐‐JWT依赖‐‐>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
Controller类,使用jjwt工具类来解析Authorization头中存储的JWT内容
@GetMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication,
HttpServletRequest request) {
String header = request.getHeader("Authorization");
String token = null;
if (header != null) {
token = header.substring(header.indexOf("bearer") + 7);
} else {
token = request.getParameter("access_token");
}
return Jwts.parser()
.setSigningKey("123123".getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token)
.getBody();
}
config
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new
JwtAccessTokenConverter();
//配置JWT使用的秘钥
accessTokenConverter.setSigningKey("123123");
return accessTokenConverter;
}
}
在授权服务器配置中指定令牌的存储策略为JWT
用密码模式测试
有时候我们需要扩展JWT中存储的内容,这里我们在JWT中扩展一个 key为enhance,value为
enhance info 的数据。
继承TokenEnhancer实现一个JWT内容增强器
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
info.put("enhance", "enhance info");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
创建一个JwtTokenEnhancer实例
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
在授权服务器配置中配置JWT的内容增强器
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)throws Exception{
//配置JWT的内容增强器
TokenEnhancerChain enhancerChain=new TokenEnhancerChain();
List<TokenEnhancer> delegates=new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
.tokenStore(tokenStore) //配置存储令牌策略
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain) //配置tokenEnhancer
.reuseRefreshTokens(false) //refresh_token是否重复使用
.userDetailsService(userService) //刷新令牌授权包含对用户信息的检查
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}