SpringSecurity+Oauth2+Jwt实现toekn认证和刷新token
简单描述:最近在处理鉴权这一块的东西,需求就是用户登录需要获取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工具使用帮助文档 这里得好好看看 最好一步步来 繁琐的过程,我的妈呀 真的要吐了 太恶心了
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的更多相关文章
- SpringBoot + SpringSecurity + Mybatis-Plus + JWT实现分布式系统认证和授权
1. 简介 Spring Security是一个功能强大且易于扩展的安全框架,主要用于为Java程序提供用户认证(Authentication)和用户授权(Authorization)功能. ...
- 【SpringBoot技术专题】「JWT技术专区」SpringSecurity整合JWT授权和认证实现
JWT基本概念 JWT,即 JSON Web Tokens(RFC 7519),是一个广泛用于验证 REST APIs 的标准.虽说是一个新兴技术,但它却得以迅速流行. JWT的验证过程是: 前端(客 ...
- SpringBoot + SpringSecurity + Mybatis-Plus + JWT + Redis 实现分布式系统认证和授权(刷新Token和Token黑名单)
1. 前提 本文在基于SpringBoot整合SpringSecurity实现JWT的前提中添加刷新Token以及添加Token黑名单.在浏览之前,请查看博客: SpringBoot + Sp ...
- SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT实现微服务统一认证授权
OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该token(令牌)在限定时间.限定 ...
- jwt、oauth2和oidc等认证授权技术的理解
前言 jwt.oauth2.oidc等,都是和认证授权相关的规范或者解决方案,因此要理解他们,就需要从业务场景的适用性一步步的分析和认识. 一.认证授权业务场景理解 就个人目前的理解来看,一个好的软件 ...
- spring oauth2+JWT后端自动刷新access_token
这段时间在学习搭建基于spring boot的spring oauth2 和jwt整合. 说实话挺折腾的.使用jwt做用户鉴权,难点在于token的刷新和注销. 当然注销的难度更大,网上的一些方案也没 ...
- [Spring Cloud实战 | 第六篇:Spring Cloud Gateway+Spring Security OAuth2+JWT实现微服务统一认证授权
一. 前言 本篇实战案例基于 youlai-mall 项目.项目使用的是当前主流和最新版本的技术和解决方案,自己不会太多华丽的言辞去描述,只希望能勾起大家对编程的一点喜欢.所以有兴趣的朋友可以进入 g ...
- ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token
传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...
- 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权
一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...
随机推荐
- 如何下载 Ubuntu 镜像文件?
Ubuntu,是一款基于 Debian Linux 的以桌面应用为主的操作系统,内容涵盖文字处理.电子邮件.软件开发工具和 Web 服务等,可供用户免费下载.使用和分享. 但是对于国内的用户来说如果直 ...
- Flask03-Form
## Web 表单 配置 为了能够处理 web 表单,我们将使用 Flask-WTF ,该扩展封装了 WTForms 并且恰当地集成进 Flask 中. 许多 Flask 扩展需要大量的配置,因此我们 ...
- python冷知识(续)
python 冷知识 1.交互式中修改最大递归深度 大家都知道使用递归是有风险的,递归深度过深容易导致堆栈的溢出. 那到底,默认递归次数限制是多少呢? 可以使用sys这个库来查看 >>&g ...
- js进度条
第一步//====================.wrap,.circle,.percent{ position: absolute; ...
- WSL中文本地化
WSL中文本地化 Windows Subsystem for Linux(简称WSL)是一个在Windows 10上能够运行原生Linux二进制可执行文件(ELF格式)的兼容层.它是由微软与Canon ...
- 解决autocomplete=off在Chrome中不起作用的方法
大家都知道autocomplete属性是表单字段中的HTML5新属性,该属性有两种状态值,分别为"on" 和 "off",该属性可省略:省略属性值后默认值为&q ...
- 转载--- 写给Node.js学徒的7个建议
贴士 1: 在开发环境使用nodemon,在生产环境使用pm2 当你第一次开发Node.js应用的时候, 其中一件事情就是一次又一次的运行[file].js 就和揭伤疤一样. 当我第一次开发的node ...
- (三)ELK logstash input
一,input模块 input 插件官方详解: https://www.elastic.co/guide/en/logstash/current/input-plugins.html Logstash ...
- python入门007
一.深浅copy 浅拷贝:是把原列表第一层的内存地址完全拷贝一份给新列表.即只能保证对原列表中第一层地址(不可变类型)的改操作不受影响,涉及到原列表中第二层地址(可变类型)的改操作时,原列表变,新列表 ...
- WPF弹性、惯性效果应用
WPF弹性.惯性效果.已发布在了一些网站,都是这里写的 代码改编自微软示例代码库 // Copyright © Microsoft Corporation. All Rights Reserved. ...