OAuth2.0 中包括认证服务器,和资源服务器,上一篇中介绍了认证服务器的配置,以及授权码模式,如何获取授权码,通过授权码,获取jwt令牌,
那么今天来看,密码模式,在密码模式中不再需要获取授权码这一步,通过调用认证服务器提供的/oauth/token接口,传参数:客户端信息(经过Base64编码,格式为 clientId:密钥 进行编码), grant_type 授权类型,以及用户名,密码,就可以获取jwt令牌
此案例中使用上一篇的认证服务配置,做少许改动,
然后就是新加一个登陆服务器,新建一个springboot项目,用作用户登陆,
登陆成功后,通过 SpringCloud FeignClient远程调用认证服务器/oauth/token,提交客户端id和密钥(经过Base64编码) grant_type 授权类型,还有用户账号密码,获取jwt令牌以及用户信息
后续客户端带token即可,资源服务进行验证(这一部分,后续在更新),
注意!!!
这里我只贴资源服务的代码了,后面会贴上认证服务器新加的配置
springcloud版本
1. pom文件核心依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
1.在资源服务中,需要ResouceServerConfig 去 extends ResourceServerConfigurerAdapter
此配置类中配置了spring-serurity的安全策略,
还有对令牌的验证,(此案例目前只是通过密码模式从认证服务器获取jwt令牌,后续如果客户端通过令牌访问资源,那么可能就考虑要验证jwt令牌,后续慢慢更新)
直接看代码
package com.youdu.resources.service.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @author Sir_小三
* @date 2020/1/31--11:39
*/
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID="res1";
@Autowired
private TokenStore tokenStore;
/**
* 资源服务设置tokenStore,服务自己验证 ,如果使用对称加密,使用相同密钥
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);//资源id
resources.tokenStore(tokenStore);
}
/**
* login 和 register 不需要认证。其他请求都需要进行认证
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/login","/register").permitAll()
.antMatchers("/**").authenticated();
}
}
4.JwtTokenConfig 配置类,认证服务器使用对称加密,资源服务器校验jwt令牌也要使用相同的密钥,(不过对于此案例,目前只是通过密码模式从认证服务器获取jwt令牌,所以此配置也影响不大,不过后续肯定是需要的)
package com.youdu.resources.service.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**资源服务和认证服务使用相同对称加密进行解析令牌
* @author Sir_小三
* @date 2020/1/30--14:34
*/
@Configuration
public class TokenConfig {
//对称加密,密钥,也可采用非对称加密
private String SIGNING_KRY="lyj123";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KRY);
return converter;
}
}
5.还有一个重要的,UserServiceDetail 配置
这里SecurityUser 去继承security.core.userdetails.User提供的user
此类来验证登陆用户密码
(这里的UserServiceDetail,和SecurityUser )类不变,跟上一篇认证服务器的配置一致
package com.youdu.distributed.authentication.service;
import com.youdu.distributed.authentication.entity.SecurityUser;
import com.youdu.distributed.authentication.entity.TbUser;
import com.youdu.distributed.authentication.mapper.TbUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author Sir_小三
* @date 2020/1/30--12:54
*/
@Service
public class UserServiceDetail implements UserDetailsService {
@Autowired
private TbUserMapper tbUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
TbUser tbUser = new TbUser();
tbUser.setName(username);
List<TbUser> page = tbUserMapper.page(tbUser, null);
TbUser tbUser1 = page.get(0);
SecurityUser securityUser = new SecurityUser(username, tbUser1.getPassword(),grantedAuthorities);
return securityUser;
}
}
package com.youdu.distributed.authentication.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
/**
* @author Sir_小三
* @date 2020/1/30--18:56
*/
public class SecurityUser extends User {
public SecurityUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public SecurityUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}
主要看下登陆接口,提供注册用户,和登陆用户两个接口。注册用户就是添加用户,密码进行加密。
登陆接口,首先判断账号密码,若ok,则远程调用认证服务器,获取jwt令牌,最后在封装用户信息,一起返回
package com.youdu.resources.service.controller;
import com.youdu.resources.service.entity.TbUser;
import com.youdu.resources.service.entity.UserLoginDTO;
import com.youdu.resources.service.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public TbUser postUser(@RequestParam("username") String username, @RequestParam("password") String password) {
/* 参数判断,省略 */
return userService.insertUser(username, password);
}
@PostMapping("/login")
public UserLoginDTO login(@RequestParam("username") String username, @RequestParam("password") String password) {
//参数判断,省略
return userService.login(username, password);
}
@GetMapping("/test")
public String test() {
return "test---api";
}
}
这里是service代码
调用认证服务器的时候,这里需要将客户端id和密钥进行编码,第二个参数为授权类型,后面两个为用户账号密码
(个人理解对于访问认证服务器进行认证的,都属于客户端,那么也就应该都配置在oauth_client_details表中,客户端需要获取令牌,那就带着自己的身份来,认证服务器从表中查询,有没有这号人,有发放令牌,没有那就对不起了,拜拜)
JWT jwt=client.getToken("Basic cmVzb3VyY2VzLXNlcnZpY2U6MTIzNDU2","password",username,password);
package com.youdu.resources.service.service;
import com.youdu.resources.service.client.AuthServiceClient;
import com.youdu.resources.service.entity.JWT;
import com.youdu.resources.service.entity.TbUser;
import com.youdu.resources.service.entity.UserLoginDTO;
import com.youdu.resources.service.exception.UserLoginException;
import com.youdu.resources.service.mapper.TbUserMapper;
import com.youdu.resources.service.utils.BPwdEncoderUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author Sir_小三
* @date 2020/1/31--16:40
*/
@Service
public class UserService {
@Autowired
private AuthServiceClient client;
@Autowired
private TbUserMapper tbUserMapper;
public TbUser insertUser(String username, String password) {
TbUser user=new TbUser();
user.setName(username);
user.setPassword(BPwdEncoderUtil.BCryptPassword(password));
tbUserMapper.insert(user);
return user;
}
public UserLoginDTO login(String username, String password) {
TbUser user=new TbUser();
user.setName(username);
List<TbUser> page = tbUserMapper.page(user, null);
TbUser tbUser = page.get(0);
if (null == tbUser) {
throw new UserLoginException("error username");
}
if(!BPwdEncoderUtil.matches(password,tbUser.getPassword())){
throw new UserLoginException("error password");
}
// 获取token
JWT jwt=client.getToken("Basic cmVzb3VyY2VzLXNlcnZpY2U6MTIzNDU2","password",username,password);
// 获得用户菜单
if(jwt==null){
throw new UserLoginException("error internal");
}
UserLoginDTO userLoginDTO=new UserLoginDTO();
userLoginDTO.setJwt(jwt);
userLoginDTO.setUser(tbUser);
return userLoginDTO;
}
}
这里面用到两个类JWT和UserLoginDTO,我都贴上来
省略getset方法
package com.youdu.resources.service.entity;
public class JWT {
private String access_token;
private String token_type;
private String refresh_token;
private int expires_in;
private String scope;
private String jti;
此类中两个属性,JWT令牌,以及user 用户信息,最终返回这个类给客户端
package com.youdu.resources.service.entity;
public class UserLoginDTO {
private JWT jwt;
private TbUser user;
public JWT getJwt() {
return jwt;
}
public void setJwt(JWT jwt) {
this.jwt = jwt;
}
public TbUser getUser() {
return user;
}
public void setUser(TbUser user) {
this.user = user;
}
}
还有一个BPwdEncoderUtil工具类,加密密码,和验证密码,传入密码,和数据库比较
package com.youdu.resources.service.utils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BPwdEncoderUtil {
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
/**
* 用BCryptPasswordEncoder
* @param password
* @return
*/
public static String BCryptPassword(String password){
return encoder.encode(password);
}
/**
*
* @param rawPassword
* @param encodedPassword
* @return
*/
public static boolean matches(CharSequence rawPassword, String encodedPassword){
return encoder.matches(rawPassword,encodedPassword);
}
}
声明FeignClient客户端
package com.youdu.resources.service.client;
import com.youdu.resources.service.entity.JWT;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author Sir_小三
* @date 2020/1/31--13:52
*/
@FeignClient("distributed-authentication")
public interface AuthServiceClient {
@PostMapping("/oauth/token")
JWT getToken(@RequestHeader("Authorization") String authorization,
@RequestParam("grant_type")String type,
@RequestParam("username") String username,
@RequestParam("password") String password
);
}
全局异常处理也贴一下吧
package com.youdu.resources.service.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
public class ExceptionHandle {
@ExceptionHandler(UserLoginException.class)
public ResponseEntity<String> handleException(Exception e) {
return new ResponseEntity(e.getMessage(), HttpStatus.OK);
}
}
package com.youdu.resources.service.exception;
public class UserLoginException extends RuntimeException{
public UserLoginException(String message) {
super(message);
}
}
**=====================================================**
接下来补上认证服务器补充的配置,在认证服务器SecurityConfig配置类中
添加认证管理器的bean,注意看注释
/**
* //配置认证管理器(使用密码模式,必须要配置)
* 注意!!!这里重写父类的authenticationManagerBean方法,方法名写错,栈溢出error,不能粗心
* @return
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
然后在AuthorizationServer配置类中注入AuthenticationManager管理器
@Autowired
private AuthenticationManager authenticationManager;
最后在这里配置AuthenticationManager
/**
* 配置令牌访问端点url,和令牌服务(令牌生成策略,如何生成)
* /oauth/authorize 授权端点
* 获取授权码
* http://localhost:8762/oauth/authorize?response_type=code&client_id=resources-service&scope=all&client_secret=123456&redirect_uri=http://www.baidu.com
* /oauth/token 获取令牌端点(发送post请求,获取token,code=申请的授权码)
*
* /oauth/confirm_access 用户确认授权端点
* /oauth/error 授权服务错误信息端点
* /oauth/check_token 用于资源服务访问的令牌解析端点
* /oauth/token_key 提供公有密钥的端点,如果使用jwt令牌的话
* 授权端点url应该被spring-security 保护起来,只供授权用户访问
* @param endpoints
* @throws Exception
*/
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)//认证管理服务,密码模式必须配置,还有userServiceDetail
.authorizationCodeServices(authorizationCodeServices)//授权码模式
.tokenStore(tokenStore)
//在这里设置jwtAccessTokenConverter,发现是ok的,可以生成jwt令牌
.accessTokenConverter(jwtAccessTokenConverter)
//.userDetailsService(userServiceDetail)//密码模式要配
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
到这里核心代码就贴完了,可以调用注册接口,添加一个用户,然后进行登陆,获取jwt令牌以及用户信息
11,有不清除的或者想要源码的,可加我微信,xiaosang953038659,谢谢你看到最后,嘻嘻