认证服务中心与资源服务器结合公钥与私钥进行令牌发放与校验、以及JDBC方式下的多客户端授权
前言
使用Spring Security OAuth2搭建认证服务中心、资源服务器、配置JDBC方式下的多客户端。实现:支持多个不同企业、个人应用与支付宝、微信等平台交互,得到他们的资源服务信息
的类似效果。
常见场景:第三方登录、扫描登录等。
例如:网站微信登录,需要先到微信开发平台申请创建一个应用网站,然后通过申请的应用ID和密匙与微信认证服务器进行通信得到Token,使用Token与微信资源服务器通信,得到想要的资源。
这里网站应用就是指客户端,也就是不同企业、用户在不同平台下创建的一个客户端应用。
搭建认证服务中心
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</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.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
新建oauth_client_details表
OAuth2中,客户端信息默认使用内存进行存储。这里通过JdbcClientDetailsService类从数据库的
oauth_client_details
表内查询客户端列表,在Oauth2源码中会操作该表,以此实现多客户端的添加、删除、更新,这种方式更加灵活方便可控。
注意: 当使用Jdbc方式来存储认证信息时,即使配置clients参数,也是没有任何作用的,只会使用数据库方式来读取客户端信息
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL COMMENT '客户端ID,主要用于标识对应的应用',
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL COMMENT '客户端秘钥,BCryptPasswordEncoder加密',
`scope` varchar(256) DEFAULT NULL COMMENT '对应的范围',
`authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '认证模式',
`web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '认证后重定向地址',
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL COMMENT '令牌有效期',
`refresh_token_validity` int(11) DEFAULT NULL COMMENT '令牌刷新周期',
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
导入1条初始化数据,这条数据就好比是向支付宝、微信等平台申请创建的一个客户端应用
INSERT INTO `oauth_client_details` VALUES ('zd', null, '$2a$10$PYI5cvzBMRkjqeC6I8KNC.RdzIeDqLGEdeG6sQf1zLqg7e9Kvrsfu', 'web', 'authorization_code,password,refresh_token,client_credentials', 'http://localhost', null, '43200', '43200', null, null);
配置授权服务器
声明一个授权服务器继承 AuthorizationServerConfigurerAdapter,添加@EnableAuthorizationServer注解。
@Configuration
//@EnableAuthorizationServer注解告诉Spring这个应用是OAuth2的认证中心
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//数据源,用于从数据库获取数据进行认证操作,测试可以从内存中获取
@Autowired
private DataSource dataSource;
//jwt令牌转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
//SpringSecurity 用户自定义授权认证类
@Autowired
UserDetailsService userDetailsService;
//授权认证管理器
@Autowired
AuthenticationManager authenticationManager;
//令牌持久化存储接口
@Autowired
TokenStore tokenStore;
@Autowired
private CustomUserAuthenticationConverter customUserAuthenticationConverter;
/***
* 客户端信息配置
* 可以用来定义一个基于内存的或者JDBC的客户端信息服务
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).clients(clientDetails());
}
//客户端配置
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
/***
* 授权服务器端点配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 配置JwtAccessToken转换器
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.authenticationManager(authenticationManager)//认证管理器
.tokenStore(tokenStore) //令牌存储
.userDetailsService(userDetailsService); //用户信息service
}
/***
* 授权服务器的安全配置
* @param oauthServer
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
//读取密钥的配置
@Bean("keyProp")
public KeyProperties keyProperties() {
return new KeyProperties();
}
@Resource(name = "keyProp")
private KeyProperties keyProperties;
@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/****
* JWT令牌转换器
* @param customUserAuthenticationConverter
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
keyProperties.getKeyStore().getLocation(), //证书路径 ybzy.jks
keyProperties.getKeyStore().getSecret().toCharArray()) //证书秘钥 adminkeystore
.getKeyPair(
keyProperties.getKeyStore().getAlias(), //证书别名 aliaskey
keyProperties.getKeyStore().getPassword().toCharArray()); //证书密码 adminkey
converter.setKeyPair(keyPair);
//配置自定义的CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
}
自定义JwtAccessToken转换器
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Autowired
UserDetailsService userDetailsService;
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
LinkedHashMap response = new LinkedHashMap();
String name = authentication.getName();
response.put("username", name);
Object principal = authentication.getPrincipal();
UserJwt userJwt = null;
if (principal instanceof UserJwt) {
userJwt = (UserJwt) principal;
} else {
//refresh_token默认不去调用userdetailService获取用户信息,这里手动去调用,得到UserJwt
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
userJwt = (UserJwt) userDetails;
}
response.put("name", userJwt.getName());
response.put("id", userJwt.getId());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}
@Data
public class UserJwt extends User {
//用户ID
private String id;
//用户名字
private String name;
public UserJwt(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
}
配置Security
继承WebSecurityConfigurerAdapter 使用@EnableWebMvcSecurity 注解开启Spring Security的功能。
@Configuration
@EnableWebSecurity
@Order(-1)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/***
* 忽略安全拦截的URL
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/oauth/login",
"/oauth/logout", "/oauth/toLogin", "/login.html", "/css/**", "/img/**", "/js/**");
}
/***
* 创建授权管理认证对象
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
/***
* 采用BCryptPasswordEncoder对密码进行编码
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/****
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 由于使用的是JWT,这里不需要csrf
.httpBasic() //启用Http基本身份验证
.and()
.formLogin() //启用表单身份验证
.and()
.authorizeRequests() //限制基于Request请求访问
.anyRequest()
.authenticated(); //其他请求都需要经过验证
//开启自定义表单登录
//http.formLogin().loginPage("/oauth/toLogin")//设置访问登录页面的路径
// .loginProcessingUrl("/oauth/login");//设置执行登录操作的路径
}
}
自定义UserDetailsService
实现UserDetailsService接口,并且重写loadUserByUsername方法,实现登录认证、授权逻辑
/*****
* 自定义授权认证类
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
ClientDetailsService clientDetailsService;
@Autowired
private UserFeign userFeign;
/****
* 自定义授权认证
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//取出身份,如果身份为空说明没有认证
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证client_id和client_secret
if (authentication == null) {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
if (clientDetails != null) {
//秘钥
String clientSecret = clientDetails.getClientSecret();
//静态方式
//return new User(username,new BCryptPasswordEncoder().encode(clientSecret), AuthorityUtils.commaSeparatedStringToAuthorityList(""));
//数据库查找方式
return new User(username, clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList(""));
}
}
if (StringUtils.isEmpty(username)) {
return null;
}
//TODO 根据用户名查询用户信息,这里构造一个用户密码,
String pwd = new BCryptPasswordEncoder().encode("userPassword");
//此角色信息将存在于jwt中 资源服务使用@PreAuthorize("hasAnyAuthority('admin')")进行权限控制
String permissions = "user,admin";
UserJwt userDetails = new UserJwt(username, pwd, AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
return userDetails;
}
}
配置application.yml
生成私钥和公钥 , 参考 JWT的使用–生成私钥和公钥
server:
port: 8888
servlet:
context-path: /auth
spring:
redis:
host: 127.0.0.1
port: 6379
password:
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/yb?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
username: root
password: 123456
encrypt:
key-store:
location: classpath:/ybzy.jks
secret: adminkeystore
alias: aliaskey
password: adminkey
搭建资源服务器
配置公钥
认证服务生成令牌采用非对称加密算法,认证服务采用私钥加密生成令牌,对外向资源服务提供公钥,资源服务使用公钥 来校验令牌的合法性。
将公钥拷贝到 publickey.txt文件中,将此文件拷贝到资源服务工程的classpath下
添加资源配置类
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
* 公钥
*/
private static final String PUBLIC_KEY = "publickey.txt";
/**
* 定义JwtTokenStore,使用jwt令牌
*
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/**
* 定义JJwtAccessTokenConverter,使用jwt令牌
*
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
*
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
/**
* Http安全配置,对每个到达系统的http请求链接进行校验
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers("/test1", "/test2/**").permitAll()
.anyRequest().authenticated();//其他地址需要认证授权
}
}
添加访问资源
@RestController
@PreAuthorize("hasAnyAuthority('admin')")
public class AuthController {
@RequestMapping("/test")
public String test(){
return "Hello world!";
}
}
配置application.properties
server.port=9999
server.servlet.context-path=/resource
四种授权模式
1.授权码模式
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。
授权码授权流程
1、客户端请求第三方授权
2、用户(资源拥有者)同意给客户端授权
3、客户端获取到授权码,请求认证服务器申请令牌
4、认证服务器向客户端响应令牌
5、客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权
6、资源服务器返回受保护资源
申请授权码
1.请求认证服务获取授权码
A网站提供一个链接,用户点击后就会跳转到B网站,授权用户数据给A网站使用。
http://localhost:8888/auth/oauth/authorize?client_id=zd&response_type=code&scop=web&redirect_uri=http://localhost
参数说明:
client_id:客户端id,和授权配置类中设置的客户端id一致, 让资源服务提供者知道是谁在请求
response_type:授权码模式固定为code,要求返回授权码code
scop:客户端范围,和授权配置类中设置的scop一致,要求的授权范围
redirect_uri:跳转uri,当授权码申请成功或拒绝后会跳转到此地址,并在后边带上code参数(授权码)
2.跳转到登录页面,进行登录
用户跳转后,B网站会要求用户登录,然后询问是否同意给予 A网站授权。
以客户端ID与客户端密匙登录, Spring Security接收到请求会调用UserDetailsService接口的loadUserByUsername方法查询用户正确的密码。
3.进入授权页面,进行授权
用户表示同意,这时B网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个授权码
返回授权码:认证服务携带授权码跳转redirect_uri, 每一个授权码只能使用一次
申请令牌
拿到授权码后,申请令牌。
A网站拿到授权码以后,就可以在后端,向B网站请求令牌。
http://localhost:8888/auth/oauth/token
grant_type参数的值是authorization_code,表示采用的授权方式是授权码,
code参数是上一步拿到的授权码,
redirect_uri参数是令牌颁发后的回调网址。
认证失败服务端返回 401 Unauthorized
http Basic认证是http协议定义的一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编码,放在header中请求服务端。
需要使用 http Basic认证
B网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段 JSON 数据
access_token:访问令牌,携带此令牌访问资源
token_type:有MAC Token与Bearer Token两种类型,两种的校验算法不同,RFC 6750建议Oauth2采用 Bearer Token
refresh_token:刷新令牌,使用此令牌可以延长访问令牌的过期时间。
expires_in:过期时间,单位为秒。
scope:范围,与定义的客户端范围一致。
jti:当前token的唯一标识
校验令牌
http://localhost:8888/auth/oauth/check_token?token=令牌
exp:过期时间,long类型,距离1970年的秒数(new Date().getTime()可得到当前时间距离1970年的毫秒数)。
user_name: 用户名
client_id:客户端Id,在oauth_client_details中配置
scope:客户端范围,在oauth_client_details表中配置
jti:与令牌对应的唯一标识
刷新令牌
刷新令牌是当令牌快过期时重新生成一个令牌,它于授权码授权和密码授权生成令牌不同,刷新令牌不需要授权码也不需要账号和密码,只需要一个刷新令牌、客户端id和客户端密码。
B网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。
http://localhost:8888/auth/oauth/token
grant_type : 固定为 refresh_token,表示要求更新令牌
refresh_token:刷新令牌(注意不是access_token,而是refresh_token),用于更新令牌的令牌
basic认证参数: client_id和client_secret参数用于确认身份
刷新令牌成功,会重新生成新的访问令牌和刷新令牌,令牌的有效期也比旧令牌长。
刷新令牌通常是在令牌快过期时进行刷新。
资源服务授权
1.资源服务授权过程
1 、客户端请求认证服务申请令牌
2、认证服务生成令牌。认证服务采用非对称加密算法,使用私钥生成令牌。
3、客户端携带令牌访问资源服务。客户端在Http header 中添加: Authorization:Bearer 令牌。
4、资源服务请求认证服务校验令牌的有效性。资源服务接收到令牌,使用公钥校验令牌的合法性。
5、令牌有效,资源服务向客户端响应资源信息
2.资源服务授权测试
请求时没有携带令牌:
请求时携带令牌:
在http header中添加 Authorization: Bearer 令牌
当输入错误的令牌时:
2.密码模式
密码模式(Resource Owner Password Credentials)与授权码模式的区别是申请令牌不再使用授权码,而是直接通过用户名和密码即可申请令牌
只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。
当令牌没有过期时同一个用户再次申请令牌则不再颁发新令牌。
授权过程
1.A网站要求用户提供 B网站的用户名和密码。拿到以后,A 就直接向B请求令牌。
2.B网站验证身份通过后,直接给出令牌。这时不需要跳转,而是把令牌放在JSON数据里面,作为 HTTP 回应,A 因此拿到令牌。
获取令牌
HTTP请求以及包含参数:
http://localhost:8888/auth/oauth/token
grant_type:密码模式授权填写password
username:账号
password:密码
参数使用x-www-form-urlencoded方式传输
需要使用 http Basic认证
3.隐藏模式
适用于纯前端应用,必须将令牌储存在前端 允许直接向前端颁发令牌。该方式没有授权码中间步骤,所以称为授权码隐藏式。
这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。
1.A 网站提供一个链接,要求用户跳转到 B 网站,授权用户数据给 A 网站使用。
https://b.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
2.用户跳转到 B 网站,登录后同意给予 A 网站授权。这时,B网站就会跳回redirect_uri参数指定的跳转网址,并且把令牌作为URL 参数,传给A网站。
4.凭证模式
适用于没有前端的命令行应用,即在命令行下请求令牌。
1.A 应用在命令行向 B 发出请求。
https://oauth.b.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
grant_type=client_credentials : 表示采用凭证式
client_id和client_secret : 用来让B确认A的身份
2.B 网站验证通过以后,直接返回令牌。
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。
认证中心集成认证服务
认证过程
1、用户登录,请求认证服务
2、认证服务认证通过,生成jwt令牌,将jwt令牌及相关信息写入Redis,并且将身份令牌写入cookie
3、用户访问资源页面,携带cookie到网关
4、网关从cookie获取token,并查询Redis校验token,如果token不存在则拒绝访问,否则放行
5、用户退出,请求认证服务,清除redis中的token,并且删除cookie中的token
实现逻辑
token封装
@Data
public class AuthToken implements Serializable{
//令牌信息 jwt
String accessToken;
//刷新token(refresh_token)
String refreshToken;
//jwt短令牌
String jti;
}
登录接口实现
public interface AuthService {
/**
* 登录时申请令牌
* @param username 账号
* @param password 密码
* @param clientId 客户端ID
* @param clientSecret 客户端密码
* @return
*/
AuthToken login(String username, String password, String clientId, String clientSecret);
}
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public AuthToken login(String username, String password, String clientId, String clientSecret) {
//申请令牌地址
String url = "http://localhost:8888/auth/oauth/token";
//封装请求参数
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "password");
body.add("username", username);
body.add("password", password);
//封装请求头信息
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Authorization", this.getHttpBasic(clientId, clientSecret));
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
super.handleError(response);
}
}
});
ResponseEntity<Map> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, Map.class);
//处理相应结果
Map map = responseEntity.getBody();
if (map == null || map.get("access_token") == null || map.get("refresh_token") == null || map.get("jti") == null) {
//申请令牌失败
throw new RuntimeException("申请令牌失败");
}
//封装结果数据
AuthToken authToken = new AuthToken();
authToken.setAccessToken((String) map.get("access_token"));
authToken.setRefreshToken((String) map.get("refresh_token"));
authToken.setJti((String) map.get("jti"));
//将jwt存入Redis
stringRedisTemplate.boundValueOps(authToken.getJti()).set(authToken.getAccessToken(), 3600, TimeUnit.SECONDS);
return authToken;
}
/**
* 客户端ID:客户端密码 进行base64编码
*
* @param clientId 客户端ID
* @param clientSecret 客户端密码
* @return
*/
private String getHttpBasic(String clientId, String clientSecret) {
String value = clientId + ":" + clientSecret;
byte[] encode = Base64Utils.encode(value.getBytes());
return "Basic " + new String(encode);
}
}
登录API
@RestController
@RequestMapping("/oauth")
public class AuthController {
@Autowired
private AuthService authService;
@RequestMapping("/login")
@ResponseBody
public Result login(String username, String password, HttpServletResponse response) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new RuntimeException("用户名或密码不能为空");
}
//申请令牌
AuthToken authToken = authService.login(username, password, "zd", "zd");
//将jti的值存入cookie中
this.saveJtiToCookie(authToken.getJti(), response);
return new Result(true, 200, "登录成功", authToken.getJti());
}
//将令牌的短标识jti存入到cookie中
private void saveJtiToCookie(String jti, HttpServletResponse response) {
Cookie cookie = new Cookie("uid", jti);
cookie.setDomain("localhost");
cookie.setPath("/");
cookie.setMaxAge(-1);
cookie.setHttpOnly(false);
response.addCookie(cookie);
}
}
认证测试
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136853.html