大家好~昨天一直有个疑问,微服务和多模块springboot项目是怎么拦截所有请求,去解析token的(不可能每个服务都写个拦截器或者过滤器),然后自己百度了资料,按自己的想法测试了下,发现是可以的,今天写篇文章记录下。
流程:
1.准备一个springboot项目
2.引入包
3.配置类
4.测试接口
1.之前我用的是单模块的springboot项目,现在换成多模块的springboot项目
1.1新建两个maven项目 test1 test2 (test1为资源服务项目 test2 为公用服务)在父项目single_sign_on引入springboot依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<!--字符集-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
image.png
1.2 test2服务导入需要的依赖
<dependencies>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!--security权限依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
1.3 在test2里面创建token的校验配置类+SpringSecurityConfig配置类
package cn.huangsong.config;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* token的校验
* 该类继承自BasicAuthenticationFilter,在doFilterInternal方法中,
* 从http头的Authorization 项读取token数据,然后用Jwts包提供的方法校验token的合法性。
* 如果校验通过,就认为这是一个取得授权的合法请求
* @author xxm
*/
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String url = request.getRequestURI();
String header = request.getHeader(JwtUtil.AUTHORIZATION);
response.setCharacterEncoding("UTF-8");
Map<String,Object> json=new HashMap<>();
//跳过不需要验证的路径
if(null != SpringSecurityConfig.AUTH_WHITELIST&&Arrays.asList(SpringSecurityConfig.AUTH_WHITELIST).contains(url)){
//chain.doFilter将请求转发给过滤器链下一个filter , 如果没有filter那就是你请求的资源
chain.doFilter(request, response);
return;
}
if (StringUtils.isEmpty(header) || !header.startsWith(JwtUtil.TOKEN_PREFIX)) {
json.put("codeCheck", false);
json.put("msg", "Token为空");
response.getWriter().write(json.toString());
return;
}
try {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request,response);
//封装好的用户信息交给Security管理
SecurityContextHolder.getContext().setAuthentication(authentication);
//chain.doFilter将请求转发给过滤器链下一个filter , 如果没有filter那就是你请求的资源
chain.doFilter(request, response);
}catch (ExpiredJwtException e) {
json.put("codeCheck", false);
json.put("msg", "Token已过期");
response.getWriter().write(json.toString());
logger.error("Token已过期: {} " + e);
} catch (UnsupportedJwtException e) {
json.put("codeCheck", false);
json.put("msg", "Token格式错误");
response.getWriter().write(json.toString());
logger.error("Token格式错误: {} " + e);
} catch (MalformedJwtException e) {
json.put("codeCheck", false);
json.put("msg", "Token没有被正确构造");
response.getWriter().write(json.toString());
logger.error("Token没有被正确构造: {} " + e);
} catch (SignatureException e) {
json.put("codeCheck", false);
json.put("msg", "Token签名失败");
response.getWriter().write(json.toString());
logger.error("签名失败: {} " + e);
} catch (IllegalArgumentException e) {
json.put("codeCheck", false);
json.put("msg", "Token非法参数异常");
response.getWriter().write(json.toString());
logger.error("非法参数异常: {} " + e);
}catch (Exception e){
json.put("codeCheck", false);
json.put("msg", "Invalid Token");
response.getWriter().write(json.toString());
logger.error("Invalid Token " + e.getMessage());
}
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request,HttpServletResponse response) {
String token = request.getHeader(JwtUtil.AUTHORIZATION);
if (token != null) {
// 解密Token
Map map = JwtUtil.validateToken(token);
//获取用户名
String userName = map.get("userName").toString();
//获取权限
List<Map<String,String>> authorities = (List)map.get("authorities");
List<GrantedAuthority> permissions = new ArrayList<>();
if(authorities.size()>0){
for (Map<String, String> authority : authorities) {
permissions.add(new SimpleGrantedAuthority(authority.get("authority")));
}
}
if (!StringUtils.isEmpty(userName)) {
//把用户名和权限封装成UsernamePasswordAuthenticationToken交给Security管理,第二个参数是密码
return new UsernamePasswordAuthenticationToken(userName, null, permissions);
}
}
return null;
}
}
JwtUtil工具类
package cn.huangsong.config;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
/**过期时间---1小时*/
private static final int EXPIRATION_TIME = 60*60;
/**自己设定的秘钥*/
//private static final String SECRET = "023bdc63c3c5a4587*9ee6581508b9d03ad39a74fc0c9a9cce604743367c9646b";
private static final String SECRET = "123456";
/**前缀*/
public static final String TOKEN_PREFIX = "Bearer ";
/**表头授权*/
public static final String AUTHORIZATION = "Authorization";
/**
*
* 功能描述:创建Token
* @date: 2020/5/28 16:09
* @param:
* @return:
*/
public static String generateToken(UserDetails userDetails) {
Calendar calendar = Calendar.getInstance();
Date now = calendar.getTime();
// 设置签发时间
calendar.setTime(new Date());
// 设置过期时间
// 添加秒钟
calendar.add(Calendar.SECOND, EXPIRATION_TIME);
Date time = calendar.getTime();
HashMap<String, Object> map = new HashMap<>();
map.put("userName", userDetails.getUsername());
map.put("authorities",userDetails.getAuthorities());
String jwt = Jwts.builder()
.setClaims(map)
//签发时间
.setIssuedAt(now)
//过期时间
.setExpiration(time)
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
//jwt前面一般都会加Bearer
return TOKEN_PREFIX + jwt;
}
/**
*
* 功能描述: 解密Token
* @date: 2020/5/28 16:18
* @param:
* @return:
*/
public static Map validateToken(String token) {
Map<String, Object> body = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody();
return body;
}
}
SpringSecurityConfig配置类
package cn.huangsong.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
*
* @author: xxm
* 功能描述: SpringSecurity的配置
* @date: 2020/5/28 15:14
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled= true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 需要放行的URL
*/
public static final String[] AUTH_WHITELIST = {
"/login"
};
//密码编码器:
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 配置请求拦截
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
//由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
//基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
//可以匿名访问的链接
.antMatchers(AUTH_WHITELIST).permitAll()
//其他所有请求需要身份认证
.anyRequest().authenticated()
.and()
//添加token验证过滤器
.addFilter(new JWTAuthenticationFilter(authenticationManager()));
}
}
1.4 test1服务导入需要的依赖
<dependencies>
<!--web依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--mybatis-plus包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.1.2</version>
</dependency>
<!--test1服务依赖-->
<dependency>
<groupId>cn.huangsong</groupId>
<artifactId>test2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
1.5 test1 结构
image.png
代码 TestController
package cn.huangsong.controller;
import cn.huangsong.config.JwtUtil;
import cn.huangsong.entity.TUser;
import cn.huangsong.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @author huangsong
* @title: TestController
* @projectName single_sign_on
* @description: TODO
* @date 2022/2/189:22
*/
@RestController
public class TestController {
@Autowired
private UserDetailServiceImpl userControllerClient;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@RequestMapping("/test1")
public String test1(){
return "拦截失败";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public BasicAjaxResult toLogin(TUser user) {
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getLoginName());
if (userDetails!=null) {
String dbPassWord = userDetails.getPassword();
if (passwordEncoder.matches(user.getPwd(),dbPassWord)) {
//创建token
String token = JwtUtil.generateToken(userDetails);
return new BasicAjaxResult().getSuccess("登陆成功").setResultObj(token);
}
}
} catch (Exception e) {
e.printStackTrace();
return new BasicAjaxResult().getFail(e.getMessage());
}
return new BasicAjaxResult().getFail("用户名或密码错误");
}
}
代码UserDetailServiceImpl
package cn.huangsong.service;
import cn.huangsong.entity.TUser;
import cn.huangsong.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义登录认证实现类
* @author huangsong
* @date 2021/3/18 14:10
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
/**
* 加载数据库中的认证的用户的信息:用户名,密码,用户的权限列表
* @param username: 该方法把username传入进来,我们通过username查询用户的信息
(密码,权限列表等)然后封装成 UserDetails进行返回 ,交给security 。
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//去数据库查询用户是否存在
TUser TUser = userMapper.selectByUsername(username);
if(TUser == null){
throw new UsernameNotFoundException("无效的用户名");
}
//加载权限
List<GrantedAuthority> permissions = new ArrayList<>();
List<String> permission = new ArrayList<>();
//测试
permission.add("employee:add");
permission.add("employee:update");
if(permission.size()>0){
for (String s : permission) {
permissions.add(new SimpleGrantedAuthority(s));
}
}
//密码是基于BCryptPasswordEncoder加密的密文
//User是security内部的对象,UserDetails的实现类 ,
//用来封装用户的基本信息(用户名,密码,权限列表)
//四个true分别是账户启用,账户过期,密码过期,账户锁定
return new User(username, TUser.getPassword(),true,true,true,true,permissions);
}
}
2.测试
2.1过期的token/格式错误的token
image.png
image.png
image.png
2.2 直接访问test1接口,不携带token
image.png
2.3携带token访问
image.png
image.png
image.png
结束:在其他服务模块引入公共服务模块即可拦截过滤所有请求验证解析token。
以上是自己的理解+实现,有误的欢迎大家指出。