简单描述:最近在处理鉴权这一块的东西,需求就是用户登录需要获取token,然后携带token访问接口,token认证成功接口才能返回正确的数据,如果访问接口时候token过期,就采用刷新token刷新令牌(得到新的token和refresh_token),然后在访问接口返回数据,如果刷新token也过期了,就提示用户重新登录。废话不多说,直接上代码。源码在github上

使用 springboot + thymeleaf + mybatis 搭建的

//核心依赖
<!-- spring security + OAuth2 + JWT start -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency> <dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring security + OAuth2 + JWT end -->
//核心配置 application.yml文件
#toekn相关配置
token:
config:
#客户端标识,类比为token的用户名,我写的是项目名
clientId: SOJ_DEMO
#客户端安全码,类比为token的密码,我写的是假邮箱
secret: soj@123
#表示授权模式: password(密码模式),authorization_code(授权码模式)
grantTypes: password
#表示权限范围,该属性为可选项
scopes: all
#令牌的有效时长,此处为180s/60,时长为2分钟 设置短一点是为了测试刷新token
accessTokenValidity: 120
#刷新令牌的有效时长
refreshTokenValidity: 36000
#资源ID号
resourceId: SOJ_DEMO
#token签名的key,用于token对称加解密
signingKey: SOJ_SYMMETRY
#设置刷新令牌机制.true(重复使用:更新access_token时长后,refresh_toke时长不更新)。false(与true相反)
isRefreshToken: false

利用keytool工具生成密钥对(非对称秘钥 公钥私钥) 来执行签名过程  keytool工具使用帮助文档 这里得好好看看 最好一步步来 繁琐的过程,我的妈呀 真的要吐了 太恶心了

//在cmd命令提示符中执行此命令  绿色部分是变量 你可以修改
keytool -genkeypair -alias jwt -keyalg RSA -keypass xc1234 -keystore jwt.jks -storepass xc1234 

执行完之后会在C:\Users\Administrator目录下生成一个jwt.jks文件, .jks文件包含了我们的秘钥 公钥 私钥,后续会在代码中读取此文件中的公钥私钥。

把生成的jwt.jks文件放到resources目录下的certificate文件夹中,这里给一下目录结构

然后在POM文件中加入对此文件的引入

//pom文件

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins> <resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>certificate/*.jks</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>certificate/*.jks</include>
</includes>
</resource>
</resources>
</build>

下边开始代码

JwtToken主要负责token解析和加解密 和上边的jwt.jks文件打交道 读取公钥私钥

package com.xc.soj_demo.jwt;

import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date; /**
* token加解密
*
* 1.对称加解密
* 2.非对称加解密(RSA)
*
*/
@Configuration
public class JwtToken { /**
* 加载jwt.jks文件
*/
private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("certificate/jwt.jks");
private static PrivateKey privateKey = null;
private static PublicKey publicKey = null; static {
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(inputStream, "xc1234".toCharArray());
privateKey = (PrivateKey) keyStore.getKey("jwt", "xc1234".toCharArray());
publicKey = keyStore.getCertificate("jwt").getPublicKey();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 生成jwt token(非对称加密模式 公钥私钥)
*/
public static String generateTokenRSA(String subject, int expirationSeconds) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expirationSeconds * 1000);
return Jwts.builder()
.setClaims(null)
.setHeaderParam("typ", "JWT")
.setSubject(subject)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.RS256, privateKey)
.compact();
} /**
* 解析jwt token(非对称加密模式 公钥私钥)
*/
public static Claims parseTokenRSA(String token) {
if (StringUtils.isEmpty(token)) {
return null;
} try {
return Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
System.out.println("try-catch:validate is token error ");
return null;
}
} /**
* token是否过期
* @return true:过期
*/
public static boolean isTokenExpired(Date expiration) {
boolean before = expiration.before(new Date());
return before;
} /**
* 生成jwt token(对称加密模式)
*/
public static String generateToken(String subject, int expirationSeconds,
String signingKey) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expirationSeconds * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(subject)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, signingKey)
.compact();
} /**
* 解析jwt token(对称加密模式)
*/
public static String parseToken(String token,String signingKey) {
if (StringUtils.isEmpty(token)) {
return null;
}
token = StringUtils.substringAfter(token, "bearer");//定义token令牌的类型为bearer
Claims claims;
try {
claims = Jwts.parser().setSigningKey(signingKey.getBytes("UTF-8")).parseClaimsJws(token).getBody();
} catch (ClaimJwtException e) {
//源码DefaultJwtParser.Class中的处理过程是 从token中取出载荷payload部分,解析出claim(claim中存在用户信息),然后在解析是否过期,最后才抛出的异常
//所以是可以从 ClaimJwtException e中取出需要的部分 并且源码ClaimJwtException.Class类中有header和claim两个私有属性并提供了get方法
claims = e.getClaims();
} catch (UnsupportedEncodingException e) {
return null;
}
String localUser = (String) claims.get("userinfo");// 拿到当前用户
return localUser;
} }

JwtToken.java

AuthServerConfig主要负责配置令牌加载的属性,自定义用户信息到token令牌内

package com.xc.soj_demo.authenticationConfig;

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import java.util.Collection;
import java.util.HashMap;
import java.util.Map; /**
* OAuth2配置类
*
* 1.配置令牌加载的属性
* 2.自定义用户信息到token令牌内
*
*/
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired
private TokenConfig tokenConfig; /**
* 注入authenticationManager
* 来支持 password grant type
*/
@Autowired
private AuthenticationManager authenticationManager; /**
* 注入userDetailService
* 来支持 refresh_token grant type
* 人话讲 就是toekn失效 需要用到refresh_token去重新请求 /oauth/token来签发新的token和refresh_token
*/
@Autowired
private UserDetailsService userDetailService; /**
* 定义oauth/token类接口信息
*
* @description tokenConfig map
* map.get("clientId") 类比为token的用户名
* map.get("secret") 类比为token的密码
* map.get("grantTypes")表示授权类型 grant_type: password(密码模式)
* map.get("scopes")权限范围
* map.get("accessTokenValidity")token有效期
* map.get("refreshTokenValidity")刷新token有效时间
* map.get("resourceId")定义资源令牌头部,资源服务器验证令牌时用到
*
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { Map<String, String> map = tokenConfig.getConfig();
clients.inMemory()
.withClient(map.get("clientId"))
.secret("{noop}" + map.get("secret"))
.authorizedGrantTypes(map.get("grantTypes"), "refresh_token")
.scopes(map.get("scopes"))
.accessTokenValiditySeconds(Integer.parseInt(map.get("accessTokenValidity")))
.refreshTokenValiditySeconds(Integer.parseInt(map.get("refreshTokenValidity")))
.resourceIds(tokenConfig.getResourceId())
// .authorities("ADMIN")
// .redirectUris("http://localhost:8882/login") // 认证成功重定向URL
.autoApprove(true);// 自动认证 } /**
* token令牌配置
*
* @description 1.定义自定义token生成方式、tokenStore、、认证管理器
* 2.定义token加解密转换器
* 3.定义token请求方式
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(accessTokenConverter());
endpoints.authenticationManager(authenticationManager);
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
endpoints.userDetailsService(userDetailService);//支持refresh_token机制
endpoints.reuseRefreshTokens(tokenConfig.isRefreshToken());//和配置文件对应的 具体看application.yml最后一项
} /**
* OAuth2服务配置
*
* @description 1.允许/oauth/token被调用,默认deny
* 2.允许所有检查token,默认deny。必须加,否则check_token不能访问显示401未授权错误
* 3.允许表单认证
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
// .tokenKeyAccess("permitAll()")
// .checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
} /**
* 生成jwt令牌
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
/***
* 重写增强token方法,用于自定义一些token总需要封装的信息
* @return
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Authentication user = authentication.getUserAuthentication();
String userName = user.getName();
Collection<? extends GrantedAuthority> authority = user.getAuthorities();
// 得到用户名,去处理数据库可以拿到当前用户的信息和角色信息(需要传递到服务中用到的信息)
final Map<String, Object> additionalInformation = new HashMap<>();
// Map假装用户实体
Map<String, Object> userinfo = new HashMap<>();
userinfo.put("userId", "001");
userinfo.put("username", userName);
userinfo.put("authOrity", authority);
additionalInformation.put("userinfo", JSON.toJSONString(userinfo));
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
return enhancedToken;
}
};
// 生成签名的key,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式
accessTokenConverter.setSigningKey(tokenConfig.getSigningKey());
return accessTokenConverter;
} }

AuthServerConfig.java

OauthInterceprtor负责拦截oauth的异常,主要是token过期之后的处理,采用刷新令牌机制重新获取token,再次请求资源

package com.xc.soj_demo.authenticationConfig;

import com.xc.soj_demo.constant.CodeConstant;
import com.xc.soj_demo.dao.UserDao;
import com.xc.soj_demo.entity.User;
import com.xc.soj_demo.jwt.JwtToken;
import com.xc.soj_demo.util.JsonUtil;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.web.client.RestTemplate; import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*; /**
* OAuth2异常拦截类
*
* 1.对oauth错误异常进行拦截,这里主要针对令牌过期进行处理
* 2.新的令牌与刷新令牌的存储
* 3.载入用户信息到spring Security的ContextHolder中,保证后续url转发
* 4.刷新令牌过期后的返回状态
*
*/
public class OauthInterceptor extends OAuth2AuthenticationEntryPoint { @Value("${server.port}")
private String port; @Autowired
private TokenConfig tokenConfig; /**
* 在启动类中注入了restTemplate Bean
*/
@Autowired
RestTemplate restTemplate; @Resource
private UserDao dao; @Autowired
private AuthenticationManager authenticationManager; private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator(); @Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
try {
ResponseEntity<?> result = exceptionTranslator.translate(authException);
JSONObject objBody = JSONObject.fromObject(result.getBody());
String message = objBody.getString("message"); //判断是否为"访问令牌过期",如果不是则以默认的方法继续处理其他异常
if (message.contains("Access token expired")) { //根据访问令牌,解析出当前令牌用户的用户名称,密码等信息
String localUser = JwtToken.parseToken(request.getHeader("Authorization"), tokenConfig.getSigningKey());
@SuppressWarnings("unchecked")
Map<String, Object> userMap = (Map<String, Object>) JsonUtil.json2Map(localUser);
String username = (String)userMap.get("username"); //根据用户名称,从数据库获取用户的刷新令牌
String refresh_token = dao.getRefreshToken(username); //获取当前用户信息
User userObj = dao.getUserByUserName(username);
Map<String, Object> map = new HashMap<>();
map.put("code", 1);//用户存在 密码正确
map.put("userId", userObj.getUserId());
map.put("username", userObj.getUsername());
map.put("password", userObj.getPassword());
List<String> listPermission = new ArrayList<>();
listPermission.add("user::add");
listPermission.add("user::list");
listPermission.add("user::update");
listPermission.add("user::delete"); List<String> listRole = new ArrayList<>();
// listRole.add("sys_admin");
// listRole.add("admin");
listRole.add(userObj.getUserRole()); map.put("authOrity", listRole);
map.put("userPermission", listPermission); //获取OAuth2框架的配置信息,用于访问刷新令牌接口
Map<String, String> tokenMap = tokenConfig.getConfig();
Map<String,String> mapParam = new HashMap<>();
mapParam.put("username", userObj.getUsername());
mapParam.put("password", userObj.getPassword());
mapParam.put("client_id", tokenMap.get("clientId"));
mapParam.put("client_secret", tokenMap.get("secret"));
mapParam.put("grant_type", "refresh_token");//这里没有写错 采用刷新令牌的方式
mapParam.put("refresh_token", refresh_token);
try { @SuppressWarnings("unchecked")
Map<String, String> mapResult = restTemplate
.getForObject(
"http://localhost:"+port+"/oauth/token?username={username}&password={password}&client_id={client_id}&client_secret={client_secret}&grant_type={grant_type}&refresh_token={refresh_token}",
Map.class, mapParam);
// 如果刷新成功 跳转到原来需要访问的页面
//写入用户信息到公共变量中,写入信息到SecurityContext中
CodeConstant.USER_MAP = map;
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
for (String role : listRole) {
grantedAuthorityList.add(new SimpleGrantedAuthority(
role));
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
userObj.getUsername(), userObj.getPassword(), grantedAuthorityList);
Authentication authentications = authenticationManager
.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(
authentications); response.setHeader("access_token",
mapResult.get("access_token"));
// response.setHeader("refresh_token",
// mapResult.get("refresh_token")); //把新获取到的refresh_token存到数据库
dao.setRefreshToken(userObj.getUserId(),mapResult.get("access_token"));
response.setHeader("isRefreshToken", "yes");
request.getRequestDispatcher(request.getRequestURI())
.forward(request, response);
} catch (Exception e) {
// e.printStackTrace();
// 获取刷新令牌失败时(刷新令牌过期时),返回指定格式的错误信息
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.getWriter().print("{\"code\":411,\"message\":\"刷新令牌以过期,需要重新登录.\"}");
response.getWriter().flush();
}
}else{
super.commence(request,response,authException);
}
} catch (Exception e) {
e.printStackTrace();
}
} }

OauthInterceptor.java

ResourceConfiguration资源服务器配置,具体负责后台 哪些接口放开,哪些接口拦截

package com.xc.soj_demo.authenticationConfig;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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; /**
* 资源服务器-配置类
*
* 1.设置接口访问权限
* 2.token验证
*
*/
@Configuration
@EnableResourceServer
public class ResourceConfiguration extends ResourceServerConfigurerAdapter { @Value("${token.resourceId}")
private String resourceId; /**
* 定义资源服务器接口访问权限
*
* @description 1.定义无权限接口
* 2.定义接口访问权限为admin
* 3.定义接口访问权限为sys_admin
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/order/*","/getToken","/parseToken","/sys/test","/sys/login","/sys/doLogin","/js/**").permitAll()// "/order/*"资源是开放的
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
// .antMatchers("/B").hasRole("admin")
// .antMatchers("/admin").hasRole("sys_admin")
.anyRequest().authenticated(); } /**
* 定义资源服务器解析协议表头(需要与认证服务器定义的表头一致)
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(resourceId).stateless(true);
resources.authenticationEntryPoint(new OauthInterceptor());
} }

ResourceConfiguration.java

TokenConfig从配置文件读取token的相关配置

package com.xc.soj_demo.authenticationConfig;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import java.util.HashMap;
import java.util.Map; @Component
@Data
@ConfigurationProperties(prefix="token")
public class TokenConfig { private Map<String, String> config = new HashMap<>();
private String resourceId;
private String signingKey;
private boolean isRefreshToken; }

TokenConfig.java

UserDetailService 用户信息获取

package com.xc.soj_demo.authenticationConfig;

import com.xc.soj_demo.constant.CodeConstant;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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; import java.util.ArrayList;
import java.util.List;
import java.util.Map; /**
* 用户信息获取(用户名称,密码,权限)
*
*/
@Component
public class UserDetailService implements UserDetailsService { @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//这个地方可以通过username从数据库获取正确的用户信息,包括密码和权限等。
// 从user获取正确的用户信息,包括密码和权限等。
Map<String, Object> user = CodeConstant.USER_MAP;
if (user != null) {
@SuppressWarnings("unchecked")
List<String> authOrity = (List<String>) user.get("authOrity");
String PASSWORD = "{noop}" + user.get("password").toString();
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
for (String auth : authOrity) {
grantedAuthorityList.add(new SimpleGrantedAuthority(auth));
}
return new User(username, PASSWORD, grantedAuthorityList);
} else {
throw new UsernameNotFoundException("用户[" + username + "]不存在");
} } }

UserDetailService.java

WebSecurityConfig认证服务器配置

package com.xc.soj_demo.authenticationConfig;

import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /**
* Security-配置类(认证服务器)
*
* 1.配置请求URL的访问策略
* 2.自定义认证登录页面URL
* 3.配置OAuth2密码模式
*
*/
@EnableWebSecurity//开启权限验证
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)//通过表达式控制方法权限
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /**
* 配置访问策略
*
* @description 1.设置授权请求
* 2.自定义登录界面
* 3.设置使用jwt,可以允许跨域
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login")
.antMatchers("/oauth/**")
.and().authorizeRequests()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login").permitAll()
.and().csrf().disable();
} /**
* 需要配置这个支持password模式 support password grant type
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} }

WebSecurityConfig.java

核心代码到这里就结束了,下边是业务代码,和上边的关联起来 MVC那一套

常量类:

package com.xc.soj_demo.constant;

import java.util.Map;

public class CodeConstant {
//为了减少查询数据库的次数,把用户的一些信息暂时存放到这里
public static Map<String, Object> USER_MAP = null; }

Controller:

package com.xc.soj_demo.controller;

import com.xc.soj_demo.entity.User;
import com.xc.soj_demo.service.UserService;
import com.xc.soj_demo.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; @Controller
@RequestMapping("/sys")
public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); @Resource
private UserService userService; //测试页面
@RequestMapping("/test")
public String testThymeleaf(ModelMap model) {
User user = new User();
user.setUsername("盖聂");
user.setUserRole("大叔");
model.addAttribute("user", user);
return "/viewTest";
} //登录页面
@RequestMapping("/login")
public String login(ModelMap model) {
return "/login";
} @RequestMapping(value = "/doLogin")
@ResponseBody
public String login(User user) {
Map<String, Object> resultMap = userService.login(user.getUsername(), user.getPassword());
String str = JsonUtil.map2Json(resultMap);
logger.info(str);
return str;
} //测试页面
@RequestMapping("/getList")
@ResponseBody
public List<String> getList() {
List<String> list = new ArrayList<>();
list.add("aa");
list.add("bb"); return list;
}
}

LoginController.java

Dao:

package com.xc.soj_demo.dao;

import org.apache.ibatis.annotations.Mapper;
import com.xc.soj_demo.entity.User; import java.util.Map; @Mapper
public interface UserDao {
User getUserByUserName(String username); void addToken2User(Map<String, Object> map); void setRefreshToken(String userId, String refreshToken); String getPasswordByUserName(String username); String getRefreshToken(String username)throws Exception; String selectRefreshTokenByUserId(Integer userId);
}

实体类:

package com.xc.soj_demo.entity;

import lombok.Data;

@Data
public class User { private String userId;
private String username;
private String password;
private String userPermission;
private String userRole;
private String token;
private String refreshToken; }

User.java

实现类:

package com.xc.soj_demo.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.xc.soj_demo.authenticationConfig.TokenConfig;
import com.xc.soj_demo.constant.CodeConstant;
import com.xc.soj_demo.dao.UserDao;
import com.xc.soj_demo.entity.User;
import com.xc.soj_demo.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; @Service("userService")
public class UserServiceImpl implements UserService { private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); @Value("${server.port}")
private String port; @Autowired
private UserDao dao; @Autowired
HttpServletRequest request; @Autowired
RestTemplate restTemplate; @Autowired
private TokenConfig tokenConfig; @Override
public Map<String, Object> login(String username, String password) {
Map<String, Object> map = new HashMap<>();
User userObj = dao.getUserByUserName(username);
// 判断用户是否存在
if (null == userObj) {
map.put("code", 1);// 用户不存在
return map;
}
// 判断密码是否正确
if (!password.equals(userObj.getPassword())) {
map.put("code", -1);// 密码错误
return map;
}
map.put("code", 0);// 用户存在 密码正确
map.put("userId", userObj.getUserId());
map.put("username", userObj.getUsername());
String userId = userObj.getUserId(); // List<String> listAuthority = dao.getUserPermissionByUserid(userId);
//模拟 查询用户权限(查询结果 可以对用户进行增删改查)
List<String> listPermission = new ArrayList<>();
listPermission.add("user::add");
listPermission.add("user::list");
listPermission.add("user::update");
listPermission.add("user::delete"); // List<String> listRole = dao.getUserRolesByUid(userId);
//模拟 查询用户角色 (查询结果 userObj具有系统管理员 普通管理员的角色)
List<String> listRole = new ArrayList<>();
// listRole.add("sys_admin");
// listRole.add("admin");
listRole.add(userObj.getUserRole()); map.put("authOrity", listRole);
map.put("userPermission", listPermission);
map.put("password", password);
try {
map.put("access_token", getOAuthToken(map));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return map;
} /**
* 调用OAuth2的获取令牌接口
*
* @description 1.将用户信息存入公共map中 2.获取访问令牌 3.写入"刷新令牌"到数据库
*
*/
private String getOAuthToken(Map<String, Object> map) throws JsonProcessingException {
CodeConstant.USER_MAP = map; Map<String, String> tokenMap = tokenConfig.getConfig();
MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add("username", map.get("username").toString());
formData.add("password", map.get("password").toString());
formData.add("client_id", tokenMap.get("clientId"));
formData.add("client_secret", tokenMap.get("secret"));
formData.add("grant_type", tokenMap.get("grantTypes")); HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); String urlStr = "http://localhost:" + port + "/oauth/token";
Map<?, ?> resultMap = restTemplate.exchange(urlStr, HttpMethod.POST,
new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody(); if (null != resultMap) {
try {
setRefreshToken(map.get("userId").toString(), resultMap.get("refresh_token").toString());
} catch (Exception e) {
e.printStackTrace();
}
return resultMap.get("access_token").toString();
}
return null;
} /**
* 更新用户的刷新令牌
*
*/
public void setRefreshToken(String userId, String refreshToken) throws Exception {
dao.setRefreshToken(userId, refreshToken);
} public String getPasswordByUserName(String username) throws Exception {
String password = dao.getPasswordByUserName(username);
return password;
} /**
* @Description 根据用户ID获取刷新token
* @param userId
* @param refreshToken
* @return Boolean
* @author chao.song
*/
public Boolean verdictRefreshTokenByUId(Integer userId, String refreshToken) {
if (userId == null || userId <) {
return false;
}
if (refreshToken.isEmpty()) {
return false;
}
String baseRefreshToken = dao.selectRefreshTokenByUserId(userId);
if (refreshToken.equals(baseRefreshToken)) {
return true;
}
return false;
} }

UserServiceImpl.java

Json工具类

package com.xc.soj_demo.util;

import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern; public class JsonUtil { private static final ObjectMapper mObjectMapper = new ObjectMapper(); static {
mObjectMapper.configure(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
mObjectMapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
mObjectMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mObjectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
mObjectMapper.configure(Feature.ALLOW_NUMERIC_LEADING_ZEROS, true);
mObjectMapper.configure(Feature.ALLOW_NON_NUMERIC_NUMBERS, true); mObjectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
mObjectMapper.configure(DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);
mObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
mObjectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mObjectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true); DateFormat myDateFormat = new SimpleDateFormat("yyyy-MM-DD hh:mm:ss");
mObjectMapper.getSerializationConfig().with(myDateFormat);
mObjectMapper.getDeserializationConfig().with(myDateFormat);
} /**
* parameters key
*/
private static final String PARA_KEY = "parameters"; /**
* @param jsonString
* @return
*/
public static Map<?, ?> json2Map(String jsonString) {
try {
Map<?, ?> map = mObjectMapper.readValue(jsonString, Map.class);
return map;
} catch (JsonMappingException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
} /**
* @param jsonString
* @return
* @description
*/
public static List<Map<?, ?>> json2MapOfArrayList(String jsonString) {
try {
JavaType javaType = getCollectionType(ArrayList.class, Map.class);
@SuppressWarnings("unchecked")
List<Map<?, ?>> arrayList = (List<Map<?, ?>>) mObjectMapper.readValue(jsonString, javaType); return arrayList;
} catch (JsonMappingException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
} /**
* @param collectionClass
* @param elementClasses
* @return
*/
public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
return mObjectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
} /**
* @param map
* @return
* @description map to json string
*/
public static String map2Json(Map<?, ?> map) {
try {
String ret = "";
ret = mObjectMapper.writeValueAsString(map);
// remove all "\"
ret = ret.replaceAll("\\\\", "");
if (ret.contains("\"[")) {
ret = ret.replaceAll("\"\\[", "\\[");
}
if (ret.contains("]\"")) {
ret = ret.replaceAll("\\]\"", "\\]");
}
if (ret.contains("\"{")) {
ret = ret.replaceAll("\"\\{", "\\{");
}
if (ret.contains("}\"")) {
ret = ret.replaceAll("\\}\"", "\\}");
}
return ret;
} catch (Exception e) {
e.printStackTrace();
return null;
}
} /**
* @param list
* @return
*/
public static String listMap2Json(List<Map<String, Object>> list) {
try {
String ret = "";
ret = mObjectMapper.writeValueAsString(list);
// remove all "\"
ret = ret.replaceAll("\\\\", "");
return ret;
} catch (Exception e) {
e.printStackTrace();
return null;
}
} /**
* @param strContent
* @return
* @description
*/
public static String getJsonString(String strContent) {
String ret = ""; if (!strContent.contains(PARA_KEY)) {
return ret;
}
String regex = "parameters[\\s]+=";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(strContent);
if (m.find()) {
strContent = strContent.replaceFirst(regex, "");
} else {
regex = "parameters=";
p = Pattern.compile(regex);
m = p.matcher(strContent);
if (m.find()) {
strContent = strContent.replaceFirst(regex, "");
}
}
strContent = strContent.trim();
return strContent;
} /**
* @param byteArray
* @return
* @description
*/
public static String bytes2Hex(byte[] byteArray) {
StringBuffer strBuf = new StringBuffer();
for (int i = 0; i < byteArray.length; i++) {
if (byteArray[i] >= 0 && byteArray[i] <) {
strBuf.append("0");
}
strBuf.append(Integer.toHexString(byteArray[i] & 0xFF));
}
return strBuf.toString();
} /**
* method_name: mapFormatString2List
* <p>
* parameters: mapString format is: [ { id=1, time=2013-11-09 09:00:00 }, {
* id=2, time=2013-11-10 09:00:00 } ]
* <p>
* <p>
* return: List<Map<String, Object>>
*/
public static List<Map<String, Object>> mapFormatString2List(String strContent) {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
String ret = "";
String regex = "\\{[^}]+\\}"; // \\{[^}]+\\} {[^}]*}
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(strContent);
while (m.find()) {
ret = m.group();
ret = ret.replaceAll("\\{", "");
ret = ret.replaceAll("\\}", "");
ret = ret.trim();
Map<String, Object> map = transStringToMap(ret);
list.add(map);
}
return list;
} /**
* method_name: transStringToMap
* <p>
* parameters: mapString format is: id=1, time=2013-11-09 09:00:00 (delim:",",
* token:"=")
* <p>
* return: Map
*/
public static Map<String, Object> transStringToMap(String mapString) {
Map<String, Object> map = new HashMap<String, Object>();
StringTokenizer items;
for (StringTokenizer entrys = new StringTokenizer(mapString, ","); entrys.hasMoreTokens(); map
.put(items.nextToken().trim(), items.hasMoreTokens() ? ((Object) (items.nextToken().trim())) : null)) {
items = new StringTokenizer(entrys.nextToken(), "=");
}
return map;
} /**
* @param str
* @return
* @description trim
*/
public static String trimAll(String str) {
if (null == str || str.length() <= 0) {
return str; } else {
return str.replaceAll("^[ ]+|[ ]+$", "");
}
} }

JsonUtil.java

mapper文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xc.soj_demo.dao.UserDao">
<resultMap id="UserResultMap" type="com.xc.soj_demo.entity.User">
<id property="userId" column="user_id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="userPermission" column="user_permission"/>
<result property="userRole" column="user_role"/>
<result property="token" column="token"/>
<result property="refreshToken" column="refresh_token" />
</resultMap> <sql id="tableName">t_user</sql> <update id="addToken2User" parameterType="java.util.Map">
update
<include refid="tableName"/>
set token = #{token} where user_id = #{userId}
</update> <select id="getUserByUserName" parameterType="String" resultMap="UserResultMap">
select * from
<include refid="tableName"/>
where username = #{username}
</select> <!-- 获取用户密码 -->
<select id="getPasswordByUserName" parameterType="String" resultType="String">
select password from <include refid="tableName" /> where username = #{username}
</select> <!-- 更新刷新令牌 -->
<update id="setRefreshToken">
update <include refid="tableName" /> set refresh_token = #{refreshToken} where user_id = #{userId}
</update> <!-- 获取用户的刷新令牌 -->
<select id="getRefreshToken" parameterType="String" resultType="String">
select refresh_token from <include refid="tableName" /> where username = #{username}
</select> <select id="selectRefreshTokenByUserId" parameterType="Integer" resultType="String">
select refresh_token from <include refid="tableName" /> where user_id = #{userId}
</select>
</mapper>

UserMapper.xml

记得在主启动类中把restTemplate注入进去,这个是要用到的

测试:

首先登陆获取token

拿到token后请求后台接口getList

然后 等2分钟  等token过期 再次请求getList

可以看到 第一次使用token去请求的时间是 15:21:26  等token过期之后再请求的时间是15:24:32  在配置文件中配置的token有效期是2分钟 刷新token时间长一点是10个小时,这时候请求也可以访问的原因  后台的刷新tokne机制起了作用。请求接口的时候使用的token是紫色方框中的那个后台验证 这个token已过期  然后刷新token机制开始工作。从已失效的tokne的载荷中 取出用户名,然后根据用户名查询用户信息,得到refresh_token,然后使用refresh_token请求/oauth/token 获取新的tokne也就是绿色方框中的access_token,最后在接着访问接口的url的到结果返回回来。刷新token机制 具体体现在OauthInterceptor.java中

SpringSecurity+Oauth2+Jwt实现toekn认证和刷新token的更多相关文章

  1. SpringBoot + SpringSecurity + Mybatis-Plus + JWT实现分布式系统认证和授权

    1. 简介   Spring Security是一个功能强大且易于扩展的安全框架,主要用于为Java程序提供用户认证(Authentication)和用户授权(Authorization)功能.    ...

  2. 【SpringBoot技术专题】「JWT技术专区」SpringSecurity整合JWT授权和认证实现

    JWT基本概念 JWT,即 JSON Web Tokens(RFC 7519),是一个广泛用于验证 REST APIs 的标准.虽说是一个新兴技术,但它却得以迅速流行. JWT的验证过程是: 前端(客 ...

  3. SpringBoot + SpringSecurity + Mybatis-Plus + JWT + Redis 实现分布式系统认证和授权(刷新Token和Token黑名单)

    1. 前提   本文在基于SpringBoot整合SpringSecurity实现JWT的前提中添加刷新Token以及添加Token黑名单.在浏览之前,请查看博客:   SpringBoot + Sp ...

  4. SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT实现微服务统一认证授权

      OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该token(令牌)在限定时间.限定 ...

  5. jwt、oauth2和oidc等认证授权技术的理解

    前言 jwt.oauth2.oidc等,都是和认证授权相关的规范或者解决方案,因此要理解他们,就需要从业务场景的适用性一步步的分析和认识. 一.认证授权业务场景理解 就个人目前的理解来看,一个好的软件 ...

  6. spring oauth2+JWT后端自动刷新access_token

    这段时间在学习搭建基于spring boot的spring oauth2 和jwt整合. 说实话挺折腾的.使用jwt做用户鉴权,难点在于token的刷新和注销. 当然注销的难度更大,网上的一些方案也没 ...

  7. [Spring Cloud实战 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT实现微服务统一认证授权

    一. 前言 本篇实战案例基于 youlai-mall 项目.项目使用的是当前主流和最新版本的技术和解决方案,自己不会太多华丽的言辞去描述,只希望能勾起大家对编程的一点喜欢.所以有兴趣的朋友可以进入 g ...

  8. ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token

    传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...

  9. 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权

    一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...

随机推荐

  1. 平时Chrome中用的一些插件

    一.chrome://extensions Adblock Plus Dark Reader 让网站黑色主题 Infinity 新标签页 一个比较流行的新标签页工具 GNOME Shell integ ...

  2. 113资讯网——NGINX 502 Bad Gateway——解决方案

    NGINX 502 Bad Gateway错误出现的原因较多,对于后端连接PHP服务的场景下,常见的原因有php服务响应超时,php进程不足等引起的一类服务器错误. 发生原因: PHP FastCGI ...

  3. 前端丨如何使用 tcb-js-sdk 实现图片上传功能

    前言 tcb-js-sdk 让开发者可以在网页端使用 JavaScript 代码服务访问云开发的服务,以轻松构建自己的公众号页面或者独立的网站等 Web 服务.本文将以实现图片上传功能为例,介绍 tc ...

  4. 小白入门NAS—快速搭建私有云教程系列(一)

    什么是NAS 在日常的工作生活中,我们有大量的资料.文件需要存储在电脑或者其他终端设备中,但是这种方式需要电脑配备高容量的硬盘,而且需要随时随地的带着,这样是不是很麻烦? 那么,今天,我来介绍一种家庭 ...

  5. Oracle Solaris 10图文安装

    文章目录 1. 虚拟机软件 2. solaris 10镜像 3. 安装OS 4. 允许远程使用root用户登录SSH 5. bash配置 5.1. 修改bash 5.2. 修改提示符 6. CRT连接 ...

  6. Solaris 11.4安装,映像包管理系统(IPS)搭建

    文章目录 1.下载地址 2. IPS安装准备 2.1 repo包 2.1 install-repo.ksh 2.2 校验文本 3. Solaris系统安装 3.1 虚拟机软件 3.2 安装os 3.3 ...

  7. Wooden Stricks——两个递增条件的线性DP

    题目 一堆n根木棍.每个棒的长度和重量是预先已知的.这些木棒将由木工机械一一加工.机器需要准备一些时间(称为准备时间)来准备处理木棍.设置时间与清洁操作以及更换机器中的工具和形状有关.木工机械的准备时 ...

  8. redis基础02-redis的5种对象数据类型

    表格引用地址:http://www.cnblogs.com/xrq730/p/8944539.html 参考书籍:<Redis设计与实现>,<Redis运维与开发> 1.对象 ...

  9. 帝国の狂欢(种树)(可撤销DP)

    题目描述 马上就要开学了!!! 为了给回家的童鞋们接风洗尘,HZOI帝国的老大决定举办一场狂欢舞会. 然而HZOI帝国头顶上的HZ大帝国十分小气,并不愿意给同学们腾出太多的地方.所以留给同学们开par ...

  10. 合并两个有序链表(剑指offer-16)

    题目描述输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则. 解答方法1:递归 /* public class ListNode { int val; List ...