Springboot集成JWT实现token令牌,同时集成SpringSecurity实现API鉴权

前言

为啥不用session和cookie,而要用token

传统的web应用,使用jsp作为前端展示的情况下,大家习惯用的手段都是用户登录后,将用户的信息放到tomcat的session中保存,返回前端时,cookies中有个jsessionId。后续的各种操作都会用到这个jsessionId从tomcat的session列表中,读取用户的信息,再根据用户的信息做各种数据的处理。
这种处理方式在单web应用中问题是不大的。但是自从分布式、微服务等理念流行之后,各个服务组件之间的session共享问题就有点头疼了,毕竟每个服务或每个节点都是独立的tomcat,跨域等问题都让人无可奈何。
这时候能采用的手段就是tomcat自身的session复制,或者spring集成redis提出的session共享,但是解决起来都是比较恶心人的,感觉都不是很好用,并且对于架构方面的可扩展性都不是很好。
这时候token机制的优势就出来了,其中比较有代表性的就是jwt(Json Web Token),签发的token中具备用户的基本信息(可以是你认为有用的信息,如用户id,用户名,角色等),只要所有的服务或节点都采用同样的token处理机制,token验证机制就能识别用户的身份。

token在前后端分离中怎么用

用户登录时,后端判断登录成功后,将有用的信息封装到token中,并且放到相应头(也有人喜欢放到相应体)中,根据项目实际情况来做。
前端拿到接收到登录响应后,从响应头中提取到token,接下来每次访问API,都将token放到请求头(也有人喜欢放到请求参数)中,并且每次接收到响应后,比较服务器返回的响应头token和本地是否一致(服务器有可能刷新token),不一致则覆盖本地token
后端接收到请求时,先验证token是否合法,不合法直接打回。再验证是否过期,过期则替换token(也可能要求再次登录),将原token放到黑名单,防止令牌泄漏。之后再去做相应的业务处理。
用户退出登录后,将原token放到黑名单,防止令牌泄漏

为啥要用spring-security,而不是自己程序去控制角色访问权限

前后端分离的架构下,比较痛苦的就是API如何鉴权,也就是哪些接口能让那些人访问。比如用户管理系统中,不是所有的用户都有权限去新增一个用户,或删除一个用户。但是所有的功能都由API实现,这时候要自己去业务逻辑里根据用户角色去判定吗?不需要的,这样太累。spring-security或帮你做到这一步,只需要你在接口上标注下,哪些角色允许调用就好了。

开始搞起来

1 添加pom依赖

1.1 添加jwt的依赖,包含了jwt本身的依赖、和security集成的依赖
<!-- JWT依赖 -->
<dependency>
     <groupId>com.nimbusds</groupId>
     <artifactId>nimbus-jose-jwt</artifactId>
     <version>5.14</version>
 </dependency>
 <dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-jwt</artifactId>
     <version>1.0.9.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt</artifactId>
     <version>0.9.0</version>
 </dependency>
1.2 添加spring-security的依赖
<!-- security支持 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.3 fastjson依赖
<!-- fastjson支持 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.40</version>
</dependency>
1.4 redis依赖
<!-- redis支持 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2 准备工作

2.1 用户角色的枚举类
package com.zyu.boot.demo.security.entity;

/**
 * JWT+SpringSecurity验证API角色枚举
 * 
 * @author zyu
 *
 */
public enum Role {
	//管理员
	admin("admin"),
	//普通用户
	user("user");
	
	//角色名称
	String role;
	
	Role(String role){
		this.role = role;
	}

	public String getRole() {
		return role;
	}

	public void setRole(String role) {
		this.role = role;
	}
	
	@Override
	public String toString() {
		return this.role;
	}
}

2.2 spring-security要求实现的用户信息类
package com.zyu.boot.demo.security.entity;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Jwt中载荷包含的用户信息
 * 同时为了和Spring-Security集成,实现了UserDetails(security要求实现)接口
 * 
 * @author zyu
 *
 */
public class JwtUser implements UserDetails {

	private static final long serialVersionUID = 4394410605122716469L;

	private String uid;// 用户ID

	private String username;// 用户名

	private List<Role> roles;// 角色

	private String password;// 密码

	public JwtUser() {
	}

	public JwtUser(String uid, String username, List<Role> roles) {
		super();
		this.uid = uid;
		this.username = username;
		this.roles = roles;
	}

	public String getUid() {
		return uid;
	}

	public JwtUser setUid(String uid) {
		this.uid = uid;
		return this;
	}

	public String getUsername() {
		return username;
	}

	public JwtUser setUsername(String username) {
		this.username = username;
		return this;
	}

	public List<Role> getRole() {
		return roles;
	}

	public JwtUser setRole(List<Role> roles) {
		this.roles = roles;
		return this;
	}

	public JwtUser setPassword(String password) {
		this.password = password;
		return this;
	}

	// 权限信息,直接使用role中的信息
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		ArrayList<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
		for (Role role : roles) {
			auths.add(new SimpleGrantedAuthority("ROLE_" + role.toString()));
		}
		return auths;
	}

	@Override
	public String getPassword() {
		return null;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}

2.3 写个JWT工具类
package com.zyu.boot.demo.utils.token;

import com.zyu.boot.demo.security.entity.JwtUser;
import com.zyu.boot.demo.security.entity.Role;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;

import java.util.*;

/**
 * JWT相关工具类
 */
public class JwtTokenUtils {

	/**
	 * 请求头中token的头
	 */
	public static final String TOKEN_HEADER = "token";

	/**
	 * token的前缀
	 */
	public static final String TOKEN_PREFIX = "Bearer ";
	/**
	 * 密钥key
	 */
	private static final String SECRET = "zyufocus0123456789zyufocus0123456789";

	/**
	 * JWT的发行人
	 */
	private static final String ISS = "zyu";

	/**
	 * 自定义信息中用户角色
	 */
	private static final String ROLE_CLAIMS = "role";
	/**
	 * 自定义信息中用户ID
	 */
	private static final String UID_CLAIMS = "uid";
	/**
	 * 自定义信息中用户名
	 */
	private static final String UNAME_CLAIMS = "uname";

	/**
	 * 过期时间是3600秒,既是60min
	 */
	public static final long EXPIRATION = 3600L * 1000;

	/**
	 * 选择了记住我之后的过期时间为1小时
	 */
	public static final long EXPIRATION_REMEMBER = 3600L * 1000;

	/**
	 * 创建token
	 * @param details 用户登录信息
	 * @param isRememberMe 是否记住我
	 * @return
	 */
	public static String createToken(JwtUser details, boolean isRememberMe) {
		// 如果选择记住我,则token的过期时间为
		long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
		HashMap<String, Object> map = new HashMap<>();
		map.put(ROLE_CLAIMS, details.getAuthorities()); // 角色名字
		map.put(UID_CLAIMS, details.getUid()); // 用户ID
		map.put(UNAME_CLAIMS, details.getUsername()); // 用户名

		return Jwts.builder().signWith(SignatureAlgorithm.HS256, SECRET) // 加密算法
				.setClaims(map) // 自定义信息
				.setIssuer(ISS) // jwt发行人
				.setSubject(details.getUsername()) // jwt面向的用户
				.setIssuedAt(new Date()) // jwt发行时间
				.setExpiration(new Date(System.currentTimeMillis() + expiration)) // key过期时间
				.compact();
	}

	/**
	 * 获取用户信息
	 * 
	 * @param token
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static JwtUser getUserDetail(String token) {
		Claims claims = getTokenBody(token);
		JwtUser user = null;
		if (claims != null && claims.size() > 0) {
			user = new JwtUser();
			if (claims.get(ROLE_CLAIMS) != null) {
				ArrayList<Role> roles = new ArrayList<>();
				ArrayList<LinkedHashMap<String, String>> list = (ArrayList<LinkedHashMap<String, String>>) claims
						.get(ROLE_CLAIMS);
				for (LinkedHashMap<String, String> l : list) {
					roles.add(Role.valueOf(l.get("authority").replace("ROLE_", "")));
				}
				user.setRole(roles);
			}
			if (claims.get(UID_CLAIMS) != null) {
				user.setUid((String) (claims.get(UID_CLAIMS)));
			}
			if (claims.get(UNAME_CLAIMS) != null) {
				user.setUsername((String) (claims.get(UNAME_CLAIMS)));
			}
		}

		return user;
	}

	/**
	 * 获取Token有效期ms
	 * 
	 * @param token
	 * @return
	 */
	public static long getExpireTime(String token) {
		Claims claims = getTokenBody(token);
		return claims.getExpiration().getTime() - claims.getIssuedAt().getTime();
	}

	/**
	 * 从token获取用户信息
	 * 
	 * @param token
	 * @return
	 */
	public static String getUsername(String token) {
		return getTokenBody(token).getSubject();
	}

	/**
	 * 从token中获取用户角色
	 * 
	 * @param token
	 * @return
	 */
	public static Set<String> getUserRole(String token) {
		List<GrantedAuthority> userAuthorities = (List<GrantedAuthority>) getTokenBody(token).get(ROLE_CLAIMS);
		return AuthorityUtils.authorityListToSet(userAuthorities);
	}

	/**
	 * 从token中获取用户ID
	 * 
	 * @param token
	 * @return
	 */
	public static String getUserID(String token) {
		return (String) getTokenBody(token).get(UID_CLAIMS);
	}

	/**
	 * 是否已过期
	 * 
	 * @param token
	 * @return
	 */
	public static boolean isExpiration(String token) {
		return getTokenBody(token).getExpiration().before(new Date());
	}

	/**
	 * 刷新token
	 * 
	 * @param token  原token
	 * @param always 强制刷新,true有效
	 * @return
	 */
	public static String refreshToken(String token, boolean always) {
		boolean canRefresh = canRefresh(token);
		String news = null;
		if (canRefresh == true || always == true) {
			Claims claims = getTokenBody(token);
			HashMap<String, Object> map = new HashMap<>();
			map.put(ROLE_CLAIMS, claims.get(ROLE_CLAIMS));
			map.put(UID_CLAIMS, claims.get(UID_CLAIMS));
			map.put(UNAME_CLAIMS, claims.get(UNAME_CLAIMS));
			news = Jwts.builder().signWith(SignatureAlgorithm.HS256, SECRET) // 加密算法
					.setClaims(map) // 自定义信息
					.setIssuer(ISS) // jwt发行人
					.setSubject((String) claims.get(UNAME_CLAIMS)) // jwt面向的用户
					.setIssuedAt(new Date()) // jwt发行时间
					.setExpiration(new Date(System.currentTimeMillis()
							+ (claims.getExpiration().getTime() - claims.getIssuedAt().getTime()))) // key过期时间
					.compact();
		} else {
			news = token;
		}
		return news;
	}

	/**
	 * 是否可刷新
	 * 
	 * @param token
	 * @return
	 */
	public static boolean canRefresh(String token) {
		Claims claims = getTokenBody(token);
		long create = claims.getIssuedAt().getTime();
		long expire = claims.getExpiration().getTime();

		// 有效期不足一半,可刷新
		return new Date().getTime() - create > (expire - create) / 2;
	}

	private static Claims getTokenBody(String token) {
		return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
	}

	/**
	 * 验证token
	 * 
	 * @param token
	 * @return
	 */
	public static boolean validateToken(String token) {
		return (isExpiration(token) == false);
	}

}
2.4 统一接口的返回信息实体类
package com.zyu.boot.demo.utils.item;

import io.swagger.annotations.ApiModelProperty;

import java.io.Serializable;

/**
 * 统一的信息返回实体
 */
public class RespEntity implements Serializable {

	private static final long serialVersionUID = -4164135841577282605L;
	
	@ApiModelProperty(value = "错误码", example = "0", dataType = "Integer")
	private Integer error_code = 0;
	@ApiModelProperty(value = "错误信息", example = "操作成功", dataType = "String")
	private String error_message = "操作成功";
	@ApiModelProperty(value = "结果", example = "{}", dataType = "json")
	private Object result;

	public Integer getError_code() {
		return error_code;
	}

	public void setError_code(Integer error_code) {
		this.error_code = error_code;
	}

	public String getError_message() {
		return error_message;
	}

	public void setError_message(String error_message) {
		this.error_message = error_message;
	}

	public Object getResult() {
		return result;
	}

	public void setResult(Object result) {
		this.result = result;
	}

	public RespEntity(Integer error_code, String error_message, Object result) {
		super();
		this.error_code = error_code;
		this.error_message = error_message;
		this.result = result;
	}

	public RespEntity(Object result) {
		this.result = result;
	}
}
2.5 redis工具类
  • 太长了,会提供源码,老铁自己下载哦
  • 关于配置springboot集成redis做序列化器,会单独写一个博客出来,这里就先用一下
2.6 redis的配置文件
#redis配置
redis:
  database: 0
  host: 172.17.0.2
  port: 6379
  password:
  jedis:
    pool:
      max-active: 8 #最大连接数
      max-wait: 6000 #最大阻塞等待时间
      max-idle: 8 #最大空闲连接
      min-idle: 0 #最小空闲连接
      timeout: 500

3 SpringSecurity相关代码

3.1 security配置类
package com.zyu.boot.demo.security.config;

import com.zyu.boot.demo.security.filter.JwtAuthenticationTokenFilter;
import com.zyu.boot.demo.security.handler.JWTAuthenticationEntryPoint;
import com.zyu.boot.demo.security.handler.MyAccessDeniedHandler;
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.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.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	// 用来实现token验证
	@Autowired
	private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

	// 权限不足处理器
	@Bean
	public MyAccessDeniedHandler myAccessDeniedHandler() {
		return new MyAccessDeniedHandler();
	};

	// 认证失败处理器
	@Bean
	public AuthenticationEntryPoint getJWTAuthenticationEntryPoint() {
		return new JWTAuthenticationEntryPoint();
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
				// 由于使用的是JWT,我们这里不需要csrf
				.csrf().disable()

				// 基于token,所以不需要session
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

				.authorizeRequests()
				// 询问请求允许访问
				.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

				// 允许对于网站静态资源的无授权访问
				.antMatchers(HttpMethod.GET, "/", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js", "/**/*.jpg",
						"/**/*.png", "/**/*.woff2","/**/*.eot", "/**/*.ttf", "/**/*.gif")
				.permitAll()
				// 设置允许访问的资源(SwaggerAPI)
				.antMatchers("/v2/api-docs", "/swagger-resources", "/swagger-resources/**", "/configuration/ui",
						"/configuration/security", "/swagger-ui.html/**", "/webjars/**", "/doc.html",
						"/v2/api-docs-ext")
				.permitAll()
				// 对于获取token的rest api要允许匿名访问
				.antMatchers("/login/**").permitAll()
				// 除上面外的所有请求全部需要鉴权认证
				.anyRequest().authenticated();

		// 不在响应头中添加禁用缓存标记
		http.headers().cacheControl().disable();
		// 配置允许加载iframe
		http.headers().frameOptions().disable();
		// 添加JWT filter
		http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

		// http.formLogin().failureHandler(getMyAuthenctiationFailureHandler());

		http
				// 权限不足处理
				.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler()).and()
				// 认证失败处理
				.exceptionHandling().authenticationEntryPoint(getJWTAuthenticationEntryPoint());
	}

}

3.2 自定义权限不足的处理类
package com.zyu.boot.demo.security.handler;

import com.alibaba.fastjson.JSON;
import com.zyu.boot.demo.utils.item.RespEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定义权限不足处理类
 * 
 * @author zyu
 *
 */
public class MyAccessDeniedHandler implements AccessDeniedHandler {
	@Override
	public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                       AccessDeniedException e) throws IOException, ServletException {
		httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
		// 返回json形式的错误信息       
		//httpServletResponse.setCharacterEncoding("UTF-8");
		httpServletResponse.setContentType("application/json");
		httpServletResponse.getWriter().write(JSON.toJSONString(new RespEntity(403, e.getMessage(), null)));
		httpServletResponse.getWriter().flush();
	}
}
3.3 自定义认证失败的处理类
package com.zyu.boot.demo.security.handler;

import com.alibaba.fastjson.JSON;
import com.zyu.boot.demo.utils.item.RespEntity;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

/**
 * 自定义认证失败的处理器
 */
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

	private static final long serialVersionUID = -8609737054427605012L;

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
			throws IOException, ServletException {
		response.setStatus(HttpStatus.UNAUTHORIZED.value());
		response.setContentType("application/json;charset=UTF-8");
		response.getWriter().write(JSON.toJSONString(new RespEntity(401, e.getMessage(), null)));
	}
}
3.4 token有效性过滤器
package com.zyu.boot.demo.security.filter;

import com.alibaba.fastjson.JSON;
import com.zyu.boot.demo.security.entity.JwtUser;
import com.zyu.boot.demo.utils.item.RespEntity;
import com.zyu.boot.demo.utils.redis.RedisUtils;
import com.zyu.boot.demo.utils.token.JwtTokenUtils;
import io.jsonwebtoken.SignatureException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @description: token过滤器,用来验证token的有效性
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

	@Autowired
	private RedisUtils redisUtils;

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String token = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
		if (token != null && token.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
			token = token.substring(JwtTokenUtils.TOKEN_PREFIX.length());
		} else {
			filterChain.doFilter(request, response);
			return;
		}

		try {
			// 查询该令牌是否在黑名单中,存在返回true
			boolean invalid = redisUtils.hasKey("tokenBlackList:" + token);
			if (invalid == false && JwtTokenUtils.isExpiration(token) == false) {
				if (JwtTokenUtils.canRefresh(token)) {
					// 将原token放入黑名单
					redisUtils.set("tokenBlackList:" + token, 1, JwtTokenUtils.getExpireTime(token) / 1000);
					// 刷新令牌
					token = JwtTokenUtils.refreshToken(token, false);
				}
				JwtUser user = JwtTokenUtils.getUserDetail(token);
				if (user != null) {
					// 制作身份认证标识,存入security域中
					UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
							user.getUsername(), null, user.getAuthorities());
					authentication.setDetails(new WebAuthenticationDetails(request));
					SecurityContextHolder.getContext().setAuthentication(authentication);
					// 将已认证的用户信息,存入request域
					request.setAttribute("currentUser", user);
				}
				// 存入request域
				request.setAttribute(JwtTokenUtils.TOKEN_HEADER, token);

			} else {
				throw new Exception("验证失败");
			}
		} catch (Exception e) {
			String msg = null;
			if (e instanceof SignatureException) {
				msg = "无效令牌";
			} else {
				msg = "认证失败";
			}
			response.setStatus(HttpStatus.UNAUTHORIZED.value());
			// 允许跨域
			response.setHeader("Access-Control-Allow-Origin", "*");
			// 允许自定义请求头token(允许head跨域)
			response.setHeader("Access-Control-Allow-Headers",
					"token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
			// 允许前端拿到的header
			response.setHeader("Access-Control-Expose-Headers",
					"token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
			response.setContentType("application/json;charset=UTF-8");
			response.getWriter().write(JSON.toJSONString(new RespEntity(401, msg, null)));
			return;
		}

		filterChain.doFilter(request, response);
	}
}

4 关键步骤来了,如何按需将token放入响应头呢

  • 核心思想就是,在登录成功或者JWT校验过滤器验证成功后,将token信息放到request域中,controller返回信息时,使用增强的方式,将token添加到响应中,这里使用springboot中的ControllerAdvice功能
package com.zyu.boot.demo.utils.respbodyadvice;

import com.zyu.boot.demo.utils.token.JwtTokenUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 用于ResponseBody注解的controller方法处理的响应中自定义响应头(添加token信息)
 * 
 * @author zyu
 *
 */
@ControllerAdvice
public class HeaderModifierAdvice implements ResponseBodyAdvice<Object> {

	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return true;
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {
		ServletServerHttpRequest ssReq = (ServletServerHttpRequest) request;
		ServletServerHttpResponse ssResp = (ServletServerHttpResponse) response;
		if (ssReq == null || ssResp == null || ssReq.getServletRequest() == null
				|| ssResp.getServletResponse() == null) {
			return body;
		}

		// 响应头添加token
		HttpServletRequest req = ssReq.getServletRequest();
		HttpServletResponse resp = ssResp.getServletResponse();
		String tokenHeader = JwtTokenUtils.TOKEN_HEADER;
		//响应头中不包含token,且request域中有token值
		if (resp.containsHeader(tokenHeader) == false && req.getAttribute(tokenHeader) != null) {
			Object token = req.getAttribute(tokenHeader);
			if (token != null) {
				resp.setHeader(tokenHeader, JwtTokenUtils.TOKEN_PREFIX + (String)token);
			}
		}
		return body;
	}

}

5 到这里,其实就万事俱备

5.1 修改原来的登录接口,去生成分角色的token
@PostMapping("/userLogin")
public RespEntity userLogin(@RequestParam("account") String account, @RequestParam("password")String password, HttpServletRequest req){
    User user = loginService.userLogin(account, password);
    if(user != null){
        ArrayList<Role> roles = new ArrayList<>();
        roles.add(Role.valueOf(user.getRole()));
        JwtUser jwtUser = new JwtUser().setRole(roles).setUid(user.getUserid());
        // token信息保存在request域,随后保存在响应头
        String token = JwtTokenUtils.createToken(jwtUser, false);
        req.setAttribute("currentUser", user);
        req.setAttribute(JwtTokenUtils.TOKEN_HEADER, token);
        return new RespEntity(user);
    }
    return new RespEntity(-1,"账户名或密码错误",null);
}
5.2 修改创建用户API的权限等级,只有管理员可访问
/**
* 创建用户
 * @param user
 * @return
 */
@PreAuthorize("hasRole('admin')")
@ApiOperation(value = "创建用户", notes = "创建用户")
@PostMapping("/create")
public User create(@RequestBody User user){
    return userService.createUser(user);
}

6 验证代码

6.1 使用普通用户登录

普通用户登录成功

  • 可以看到这里已经有了服务器返回普通用户角色的token
6.2 使用该token创建新用户

普通用户调用创建用户API

  • 这里显示权限不足了
6.3 管理员登录

管理员登录成功

  • 响应头中也返回了具有管理员角色的token
6.4 使用管理员token创建用户

管理员调用创建用户接口

  • 管理员创建该接口就成功了,很完美的实现了我们想要的鉴权效果,鼓掌!!!

7 使用git提交代码

别忘了用git保存下哦

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值