【JAVA】jwt
jwt用于登陆验证后token发放,后续请求头中携带token进行鉴权操作
SecurityConfig.java
@Configuration
@EnableWebSecurity // 开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true) // 保证post之前的注解可以使用
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
JwtAuthorizationTokenFilter authenticationTokenFilter;
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// CSRF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
.antMatchers("/templates/**", "/images/**", "/scripts/**", "/styles/**","/serveDataLogMessage").permitAll()
.mvcMatchers("/", "/home", "/basics", "/theme", "/application**", "/catalog/**", "/public/**", "/uib/**").permitAll()
.antMatchers("/ca/getBase64String", "/ca/challengeTicket", "/ca/download").permitAll()
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/developer/**").hasRole("DEVELOPER")
.antMatchers("/manager/**").hasRole("ADMIN")
.antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
// 添加JWT filter
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
JwtAuthorizationTokenFilter.java
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Autowired
UserService userService;
@Autowired
RoleService roleService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// public中获取用户信息的接口,需要解析token
String authorization = request.getHeader("Authorization");
if (StringUtils.isNotEmpty(authorization) && authorization.startsWith("Bearer ") && authorization.length() > 10) {
setUserContext(request, response);
chain.doFilter(request, response);
return;
}
String servletPath = request.getServletPath();
// 不需要token直接放行的url
if ("/ca/getBase64String".equals(request.getServletPath())) {
chain.doFilter(request, response);
return;
}
if ("/ca/challengeTicket".equals(request.getServletPath())) {
chain.doFilter(request, response);
return;
}
if ("/ca/download".equals(request.getServletPath())) {
chain.doFilter(request, response);
return;
}
if (servletPath.startsWith("/templates")
|| servletPath.startsWith("/images")
|| servletPath.startsWith("/scripts")
|| servletPath.startsWith("/styles")
|| servletPath.startsWith("/serveDataLogMessage")
|| servletPath.startsWith("/home")
|| servletPath.startsWith("/basics")
|| servletPath.startsWith("/theme")
|| servletPath.startsWith("/application")
|| servletPath.startsWith("/catalog")
|| servletPath.startsWith("/public")
|| servletPath.startsWith("/uib")
) {
chain.doFilter(request, response);
return;
}
// 无token
if (StringUtils.isEmpty(authorization) || "Bearer".equals(authorization.trim())) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "token不存在");
return;
} else {
// token无效
boolean checkToken = JwtUtils.checkToken(authorization.substring(7));
if (!checkToken) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "token无效");
return;
}
}
if (StringUtils.isNotEmpty(authorization) && authorization.startsWith("Bearer ")) {
setUserContext(request, response);
}
chain.doFilter(request, response);
}
private void setUserContext(HttpServletRequest request, HttpServletResponse response) throws IOException {
String jwtToken = request.getHeader("Authorization").substring(7);
String secret = PropertiesUtils.getConfigByYml("jwt.secret");
// 获取载荷payload
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwtToken).getBody();
ObjectMapper objectMapper = new ObjectMapper();
String payload = objectMapper.writeValueAsString(claims);
JSONObject jsonObject = JSONObject.fromObject(payload);
String userid = (String) jsonObject.get("appAccount");
if (userid != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 通过数据库获取用户角色
User user = userService.getById(userid);
List<Role> roles = new ArrayList<>();
if (user != null) {
// 获取角色和部门
// roles = roleService.getRolesByUserid(user.getId());
String[] rolesstr = user.getUserRole().split(",");
for(String r:rolesstr){
if(!StringUtils.isEmpty(r)){
Role role=new Role();
role.setId(r);
roles.add(role);
}
}
user.setRoles(roles);
} else {
// ca用户不在本地系统数据库中
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "用户" + userid + "不存在");
return;
}
// 设置角色权限
List<GrantedAuthority> authorityList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(roles)) {
for (Role role : roles) {
if (role.getId().startsWith("ROLE_")) {
authorityList.add(new SimpleGrantedAuthority(role.getId()));
} else {
authorityList.add(new SimpleGrantedAuthority("ROLE_" + role.getId()));
}
}
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userid, null, authorityList);
// 用户信息方法spring上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
JwtAuthenticationEntryPoint.java
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "认证失败");
}
}
JwtUtils.java
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
public class JwtUtils {
// public static final long EXPIRE = 1000 * 60 * 10; // token有效期10分钟
/**
* 根据用户id和昵称生成token
*
* @param appAccount 用户名
* @return JWT规则生成的token
*/
public static String getJwtToken(String appAccount) {
String secret = PropertiesUtils.getConfigByYml("jwt.secret");
int EXPIRE = Integer.parseInt(PropertiesUtils.getConfigByYml("jwt.expiration"));
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("bigData")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("appAccount", appAccount)
// HS256算法实际上就是MD5加盐值,此时SECRET就代表盐值
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
*
* @param jwtToken token字符串
* @return 如果token有效返回true,否则返回false
*/
public static boolean checkToken(String jwtToken) {
String secret = PropertiesUtils.getConfigByYml("jwt.secret");
if (StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
*
* @param request Http请求对象
* @return 如果token有效返回true,否则返回false
*/
public static boolean checkToken(HttpServletRequest request) {
String secret = PropertiesUtils.getConfigByYml("jwt.secret");
try {
// 从http请求头中获取token字符串
String jwtToken = request.getHeader("token");
if (StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(secret).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token获取会员id
*
* @param request Http请求对象
* @return 解析token后获得的用户id
*/
public static String getAppAccountByJwtToken(HttpServletRequest request) {
String secret = PropertiesUtils.getConfigByYml("jwt.secret");
String jwtToken = request.getHeader("Authorization");
if (StringUtils.isEmpty(jwtToken)) return "";
if (StringUtils.isNotEmpty(jwtToken) && jwtToken.startsWith("Bearer ")) {
jwtToken = jwtToken.substring(7);
}
try {
// 获取载荷payload
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwtToken).getBody();
ObjectMapper objectMapper = new ObjectMapper();
String payload = objectMapper.writeValueAsString(claims);
JSONObject jsonObject = JSONObject.fromObject(payload);
String appAccount = (String) jsonObject.get("appAccount");
return appAccount;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* 刷新token过期时间
*
* @param jwtToken 原始token
* @return 新token
*/
public static String refreshToken(String jwtToken) {
if (StringUtils.isEmpty(jwtToken)) return null;
String newToken = "";
try {
String secret = PropertiesUtils.getConfigByYml("jwt.secret");
int EXPIRE = Integer.parseInt(PropertiesUtils.getConfigByYml("jwt.expiration"));
// 获取载荷payload
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwtToken).getBody();
ObjectMapper objectMapper = new ObjectMapper();
String payload = objectMapper.writeValueAsString(claims);
JSONObject jsonObject = JSONObject.fromObject(payload);
// iat: jwt的签发时间
int iat = (int) jsonObject.get("iat");
// 当前时间-签发时间 < 过期时间的一半,不用重新发
// if ((System.currentTimeMillis() - iat * 1000L) < (EXPIRE / 2)) {
// return jwtToken;
// }
// exp: jwt的过期时间
// 重置过期时间
jsonObject.put("exp", ((System.currentTimeMillis() + EXPIRE) / 1000));
// 重置签发时间
jsonObject.put("iat", ((System.currentTimeMillis()) / 1000));
String newPayload = jsonObject.toString();
newToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setPayload(newPayload)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return newToken;
}
public static String refreshToken(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
}
return refreshToken(token);
}
}
PropertiesUtils.java
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class PropertiesUtils {
private static Logger logger = LoggerFactory.getLogger(PropertiesUtils.class);
private static Map<String,Properties> propertiesMap = new HashMap<String,Properties>();
/**
* 默认yml后缀文件名称
*/
private final static String DEFAULT_YML_NAME = "application.yml";
/**
* 默认properties后缀文件名称
*/
private final static String DEFAULT_PROPERTIES_NAME = "application.properties";
/**
* Yaml 文件转换
* @param yamlFileName
*/
private static void setYamlPropertiesToMap(String yamlFileName){
if (StringUtils.isBlank(yamlFileName)) {
throw new RuntimeException(yamlFileName + "不能为空");
}
try {
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
yamlFactory.setResources(new ClassPathResource(yamlFileName));
propertiesMap.put(yamlFileName,yamlFactory.getObject());
} catch (Exception e) {
logger.error("转换异常:",e);
}
}
/**
* Properties 文件转换
* @param propertiesFileName
*/
private static void setPropertiesToMap(String propertiesFileName){
if (StringUtils.isBlank(propertiesFileName)) {
throw new RuntimeException(propertiesFileName + "不能为空");
}
try {
propertiesMap.put(propertiesFileName, PropertiesLoaderUtils.loadAllProperties(propertiesFileName)); ;
} catch (IOException e) {
logger.error("IO异常:",e);
}
}
/**
* 获取yml配置文件中的值
*
* @param key 需要获取的键名
* @return 对应的值
*/
public static String getConfigByYml(String key) {
return getConfigByYml(key,DEFAULT_YML_NAME);
}
/**
* 获取yml配置文件中的值
*
* @param key 需要获取的键名
* @param configName 自定义配置文件yml后缀的名称
* @return 对应的值
*/
public static String getConfigByYml(String key, String configName) {
if(!propertiesMap.containsKey(configName)){
setYamlPropertiesToMap(configName);
}
return propertiesMap.get(configName).getProperty(key);
}
/**
* 获取properties配置文件中的值
*
* @param key 需要获取的键
* @return 对应的值
*/
public static String getConfigByProperties(String key) {
return getConfigByProperties(key,DEFAULT_PROPERTIES_NAME);
}
/**
* 获取properties配置文件中的值
*
* @param key 需要获取的键
* @param configName properties配置文件的名称
* @return 对应的值
*/
public static String getConfigByProperties(String key, String configName) {
if(!propertiesMap.containsKey(configName)){
setPropertiesToMap(configName);
}
return propertiesMap.get(configName).getProperty(key);
}
}