config层中的 MVCconfig
@Configuration @ComponentScan(basePackages = "edu.nf.demo") @EnableWebMvc public class MvcConfig implements WebMvcConfigurer { /** * 使用默认servlet处理静态资源 * @param configurer */ @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } /** * 全局跨域设置 * @param registry */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedHeaders("*") .allowedMethods("*"); } /** * 装配认证拦截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthTokenInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/login"); } }
WebInitializer
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{MvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
定义一个JWT的工具类
@Slf4j public class JwtUtils { /** * 秘钥,用于signature(签名)部分的加密和解密 */ private static final String KEY = "nf-it"; /** * 签发机构 */ private static final String ISS = "nf"; /** * 创建token * @param claims 数据载体 * @param ttl token的过期时间 * @return */ public static String createToken(Map<String, Object> claims, long ttl) { JwtBuilder builder = Jwts.builder() //获取签名秘钥,并采用HS256的加密算法进行提签名 .signWith(SignatureAlgorithm.HS256, KEY) //jwt唯一标识 .setId(UUID.randomUUID().toString()) //设置数据内容 .setClaims(claims) //设置签发人 .setIssuer(ISS) //主题 .setSubject("JWT AUTH") //签发时间 .setIssuedAt(new Date()); //设置过期时间 if(ttl > 0) { builder.setExpiration(getExpDate(ttl)); } //创建jwt字符串并返回 return builder.compact(); } /** * 获取载体的数据 * @param token jwt * @param name json载体数据中的name * @param type 载体数据的类型 * @return * @param <T> */ public static <T> T getPayload(String token, String name, Class<T> type) { return Jwts.parser() //解密 .setSigningKey(KEY) //解析token中的数据载体部分 .parseClaimsJws(token) //取出载体数据 .getBody() //根据name和type取中相应的value .get(name, type); } /** * 验证token的有效性,如果验证不通过会抛出相应的异常 * @param token */ public static void verify(String token) { Jwts.parser().setSigningKey(KEY).parseClaimsJws(token); } /** * 根据用户传的ttl计算出过期时间 * @param ttl * @return */ private static Date getExpDate(long ttl) { //当前时间 + ttl = 过期时间 Date expDate = new Date(System.currentTimeMillis() + ttl); log.debug("Token过期时间:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(expDate)); return expDate; } }
局部异常处理
public class GlobalException extends RuntimeException { private Integer errorCode; public GlobalException(Integer errorCode, String message) { super(message); this.errorCode = errorCode; } public Integer getErrorCode() { return errorCode; } }
public class LoginException extends GlobalException { public LoginException(Integer errorCode, String message) { super(errorCode, message); } }
token拦截器
/** * @author wangl * @date 2022/10/26 * token验证拦截器 */ public class AuthTokenInterceptor implements HandlerInterceptor { /** * 请求头的name */ private static final String HEADER = "Authorization"; /** * token前缀,注意有个空格 */ private static final String TOKEN_PREFIX = "Bearer "; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //解析token String token = request.getHeader(HEADER); if(token != null) { //得到jwt String jwt = token.replace(TOKEN_PREFIX, ""); //验证token,验证未通过会引发相关异常 try { JwtUtils.verify(jwt); //获取载体数据 Integer uid = JwtUtils.getPayload(jwt, "uid", Integer.class); //将其保存到请求作用域,在controller中就可以通过@RequestAttribute注解获取到 request.setAttribute("uid", uid); //放行 return true; } catch (SecurityException e) { responseView(response, "无效的签名"); } catch (MalformedJwtException e) { responseView(response, "无效的token"); } catch(ExpiredJwtException e) { responseView(response, "Token已过期"); } catch(UnsupportedJwtException e) { responseView(response, "不支持的Token类型"); } return false; } //没有token表示未登录 responseView(response, "请先登陆"); return false; } /** * 统一响应视图 * @param response * @param message */ private void responseView(HttpServletResponse response, String message) throws IOException { ResultVO vo = new ResultVO(); vo.setCode(HttpStatus.UNAUTHORIZED.value()); vo.setMessage(message); response.setContentType("application/json;charset=utf-8"); String json = new ObjectMapper().writeValueAsString(vo); response.getWriter().println(json); } }
service接口
public interface LoginService { /** * 登录认证,认证通过后颁发token * @param account * @param password * @return */ String auth(String account, String password); }
service中的impl
@Service public class LoginServiceImpl implements LoginService { @Override public String auth(String account, String password) { if("user1".equals(account) && "123".equals(password)) { //封装数据载体 Map<String, Object> map = new HashMap<>(); map.put("uid", 1001); //生成jwt String jwt = JwtUtils.createToken(map, 120000L); return jwt; } throw new LoginException(10000, "账号或密码错误"); } }
vo层
@Data public class ResultVO<T> { private Integer code; private String message; private T data; }
controller中的BaseController
public class BaseController { public <T> ResultVO<T> success(T data) { ResultVO<T> vo = new ResultVO<>(); vo.setCode(HttpStatus.OK.value()); vo.setData(data); return vo; } public ResultVO success() { ResultVO vo = new ResultVO(); vo.setCode(HttpStatus.OK.value()); return vo; } }
LoginController
@RestController @RequiredArgsConstructor public class LoginController extends BaseController { private final LoginService service; @PostMapping("/login") public ResultVO login(String account, String password, HttpServletResponse response) { String jwt = service.auth(account,password); //将jwt添加到响应头带回客户端 response.addHeader("token", jwt); return success(); } }
UserController
当我们的没有登录时,就不能读取到这个controller的方法,也不能读取这个页面
@RestController @Slf4j public class UserController extends BaseController{ /** * 如果用户未登录是不能请求这个url * @param id 从请求作用域获取的id * @return */ @GetMapping("/user") public ResultVO<String> getUser(@RequestAttribute("uid") int id) { log.debug("根据id查询用户讯息"); //测试数据 String data = "{" + id + ": user1}"; return success(data); } }
当我们输出错误时,就会报这个错
正确时
由于我们没有添加跳转接收,就算输出正确也会显示出错误,这个不用担心