JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。
JWT的由,header(标头),payload(有效载荷),singnature(签名),三个部分组成的一个Stirng类型的字符串。可以通过对字符串的编码和解密,完成WEB端的登录认证和授权。
JWT的工作流程如下图所示。
JWT实现的核心代码有两个,一个是实现拦截器接口,一个是将拦截器注入Spring容器中运行。
以下是实现拦截器接口,方法是重写前置拦截器,从请求头中获取token的数据进行判断,数据无误可放行,数据不对进行拦截。
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头中令牌
String token = request.getHeader("token");
try {
JWTUtils.verify(token);//验证令牌
return true;//放行请求
}catch (Exception e){
e.printStackTrace();
}
return false;
}
}
将拦截器注入Spring容器中,下面需要配置将JWT拦截器进行添加,然后添加下面需要拦截的路径和不需要拦截的路劲。
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/test") //其他接口token验证
.excludePathPatterns("/user/login"); //排除拦截路径
}
}
SpringSecurity是一个安全框架,它有认证和授权这两个核心功能。
用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。
用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
SpringSecurity有三个重要个实现类和一个接口,我们可以通过重写实现类里面的方法对SpringSecurity进行的配置。他们的调用流程如下。
第一个类名是UsernamePasswordAuthenticationFilter的过滤器,这个可以通过这个这类获取登录页面提交的账号和密码进行处理,并且可以配置方法的成功和失败。
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
public TokenLoginFilter() {
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}
//1 获取表单提交用户名和密码
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//获取表单提交数据
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),
new ArrayList<>()));
}
//2 认证成功调用的方法
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//认证成功,得到认证成功之后用户信息
//设置token或者设置cookie都在这
}
//3 认证失败调用的方法
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
}
}
第二个接口是UserDetailsServie的接口,这是接口是SrpingSecurity的用户数据认证接口,需要重写里面的loadUserByUsername方法,在数据库中找到相应的用户数据,复制到UserDetails的类型中返回,就可以对账号就行认证。
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询数据
//将数据库的数据复制到UserDetails的类型中
UserDetails userDetails = new UserDetails();
return userDetails;
}
第三个类是BasicAuthenticationFilter的实现类,重写doFilterInternal方法,这个就是配置权限的方法了,除了登录方法排除在外,其他方法都会进入到这个方法加载权限。然后SpringSecurity就会根据这个权限来判断请求的方法是否可以继续访问。
public class TokenAuthFilter extends BasicAuthenticationFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取当前认证成功用户权限信息
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken();
//判断如果有权限信息,放到权限上下文中
SecurityContextHolder.getContext().setAuthentication(authRequest);
chain.doFilter(request,response);
}
}
第四个配置类是WebSecurityConfigurerAdapter对SpringSecurity进行注入到Spring的容器中运行,需要重写configure的方法,配置数据。
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置特定权限
http.authorizeRequests()
.antMatchers("/admin/acl/user/**").hasAuthority("user.list")
//没有权限访问
.and().exceptionHandling()
.authenticationEntryPoint(new UnauthEntryPoint())
//跨域问题
.and().csrf().disable()
//退出路径
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate))
//添加自定义过滤器
.and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
.addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
}
}
SpringSecurity+jwt的集成,登录的整个流程。
集成jwt就是在SpringSecurity架构认证成功之后去添加token返回给前端,这个认证成功的方法就是successfulAuthentication,在这里去添加token返回。
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//认证成功,得到认证成功之后用户信息
SecurityUser user = (SecurityUser)authResult.getPrincipal();
//根据用户名生成token
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
//把用户名称和用户权限列表放到redis
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
//返回token
ResponseUtil.out(response, R.ok().data("token",token));
}
返回给前端后,前端会将token设置在请求头,之后在每次请求都会发送回来。我们可以在授权前对token进行校验,看是否授权。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取当前认证成功用户权限信息
UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
//判断如果有权限信息,放到权限上下文中
if(authRequest != null) {
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
//从header获取token
String token = request.getHeader("token");
if(token != null) {
//从token获取用户名
String username = tokenManager.getUserInfoFromToken(token);
//从redis获取对应权限列表
List permissionValueList = (List)redisTemplate.opsForValue().get(username);
Collection authority = new ArrayList<>();
for(String permissionValue : permissionValueList) {
SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
authority.add(auth);
}
return new UsernamePasswordAuthenticationToken(username,token,authority);
}
return null;
}
bilibili htmhttps://player.bilibili.com/player.html?aid=202502517&bvid=BV15a411A7kP&cid=256421944&page=1