前后端分离中使用基于jwt的token进行身份认证

基于jwt的Token认证机制可以看之前的文章:

基于JWT的Token认证机制实现

 

在前后端分离中,我们与前端约定一种身份认证机制。当用户登录的时候,我们会返回给前端一个token,前端会将token拿到并按照一定规则放到header中在下一次请求中发送给后端,后端进行token身份校验。

这里我们约定前端请求后端服务时需要添加头信息Authorization ,内容为Test:+空格+token

1.导入pom依赖

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.16</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
​
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

2.jwt工具类

@ConfigurationProperties("jwt.config")
@Component
@Data
public class JwtUtils {
    /**
     * 签名私钥
     */
    private String key;
    /**
     * 签名的失效时间
     */
    private Long ttl;
​
    /**
     * 设置认证token
     * id:登录用户id
     * subject:登录用户名
     */
    public String createJwt(String id, String name, Map<String, Object> map) {
        //1.设置失效时间
        long now = System.currentTimeMillis();
        long exp = now + ttl;
        //2.创建jwtBuilder
        JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(name)
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, key);
        //3.根据map设置claims
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            jwtBuilder.claim(entry.getKey(), entry.getValue());
        }
        jwtBuilder.setExpiration(new Date(exp));
        //4.创建token
        return jwtBuilder.compact();
    }
​
​
    /**
     * 解析token字符串获取clamis
     */
    public Claims parseJwt(String token) {
        try {
            return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            return null;
        }
    }
}

3.Spring的yml配置

jwt:
  config:
     key: test
     ttl: 360000
server:
  port: 10001

4.全局常量类

public class GlobalConstant {
    public static final String AUTHORIZATION = "Authorization";
    public static final String USER_KEY = "user_key";
}

5.通用返回类

前后端分离中,通常会与前端约定一个默认的返回格式,并定义一些不同场景下的状态码。

Result.java

@Data
@NoArgsConstructor
public class Result {
​
    private boolean success;//是否成功
    private Integer code;// 返回码
    private String message;//返回信息
    private Object data;// 返回数据
​
    public Result(ResultCode code) {
        this.success = code.success;
        this.code = code.code;
        this.message = code.message;
    }
​
    public Result(ResultCode code, Object data) {
        this.success = code.success;
        this.code = code.code;
        this.message = code.message;
        this.data = data;
    }
​
    public Result(Integer code, String message, boolean success) {
        this.code = code;
        this.message = message;
        this.success = success;
    }
​
    public static Result SUCCESS(){
        return new Result(ResultCode.SUCCESS);
    }
​
    public static Result ERROR(){
        return new Result(ResultCode.SERVER_ERROR);
    }
​
    public static Result FAIL(){
        return new Result(ResultCode.FAIL);
    }
}

ResultCode.java

public enum ResultCode {
​
    SUCCESS(true,10000,"操作成功!"),
    //---系统错误返回码-----
    FAIL(false,10001,"操作失败"),
    UNAUTHENTICATED(false,10002,"您还未登录"),
    UNAUTHORISE(false,10003,"权限不足"),
    SERVER_ERROR(false,99999,"抱歉,系统繁忙,请稍后重试!"),
​
    //---用户操作返回码  2xxxx----
    MOBILEORPASSWORDERROR(false,20001,"用户名或密码错误");
​
    //---企业操作返回码  3xxxx----
    //---权限操作返回码----
    //---其他操作返回码----
​
    //操作是否成功
    boolean success;
    //操作代码
    int code;
    //提示信息
    String message;
​
    ResultCode(boolean success,int code, String message){
        this.success = success;
        this.code = code;
        this.message = message;
    }
​
    public boolean success() {
        return success;
    }
​
    public int code() {
        return code;
    }
​
    public String message() {
        return message;
    }
​
}

6.拦截器注解

在我们后端的接口中,并不是所有接口都需要拦截,比如登录接口就不需要登录就能访问,比如有的接口有游客模式也不需要登录访问。我们自定义一个注解,然后在接下来的全局拦截器中去特殊处理这些不需要拦截的接口。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExcludeInterceptor {
   boolean value() default true;
}

7.拦截器

利用Spring的Intercepter拦截器,拦截所有请求,去校验用户是否登录。同时将用户的id获取到,设置到session中,方便代码直接获取使用。

注意:为了防止拦截器二次拦截,需要配置不拦截/error请求

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
    @Autowired
    private JwtUtils jwtUtils;
    private Logger logger = LoggerFactory.getLogger(this.getClass());
​
    protected static Collection<HandlerMethodArgumentResolver> methodArgumentResolverList;
​
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        super.addArgumentResolvers(argumentResolvers);
        if (methodArgumentResolverList != null) {
            argumentResolvers.addAll(methodArgumentResolverList);
        }
    }
​
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH")
                .allowCredentials(true).maxAge(3600);
    }
​
    /**
     * FastJson配置
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(fastJsonHttpMessageConverters());
    }
​
    @Bean
    public HttpMessageConverter fastJsonHttpMessageConverters() {
        //1. 需要定义一个converter转换消息的对象
        FastJsonHttpMessageConverter fasHttpMessageConverter = new FastJsonHttpMessageConverter();
        //2. 添加fastjson的配置信息,比如:是否需要格式化返回的json的数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        //3. 处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fasHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
        //4. 在converter中添加配置信息
        fasHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
​
        return fasHttpMessageConverter;
    }
​
    @Bean
    public SecurityInterceptor getSecurityInterceptor() {
        return new SecurityInterceptor();
    }
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration interceptor = registry.addInterceptor(getSecurityInterceptor());
        ArrayList<String> list = new ArrayList<>();
        list.add("/error");
        list.add("/static/**");
        interceptor.excludePathPatterns(list);
        super.addInterceptors(registry);
    }
​
​
    protected class SecurityInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println(request.getMethod());
            if ("OPTIONS".equals(request.getMethod())) {
                if (response != null) {
                    response.setStatus(200);
                    response.sendError(200, "通过");
                }
                return false;
            }
​
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            ExcludeInterceptor excludeInterceptor = handlerMethod.getMethodAnnotation(ExcludeInterceptor.class);
            if (excludeInterceptor == null || !excludeInterceptor.value()) {
                if (request.getHeader(GlobalConstant.AUTHORIZATION) == null) {
                    response.setStatus(401);
                    response.sendError(401, "登录失效,请重新登录");
                    return false;
                } else {
                    String authorization = request.getHeader(GlobalConstant.AUTHORIZATION);
                    String token = authorization.replace("Test: ", "");
                    Claims claims = jwtUtils.parseJwt(token);
                    if (claims == null) {
                        response.setStatus(401);
                        response.sendError(401, "登录失效,请重新登录");
                        return false;
                    }
                    String userId = claims.getId();
                    request.getSession().setAttribute(GlobalConstant.USER_KEY, userId);
​
                }
            }
            return true;
        }
​
    }
}

8.测试controller

@RestController
public class LoginController {
    @Autowired
    private JwtUtils jwtUtils;
​
    @PostMapping("/login")
    @ResponseBody
    @ExcludeInterceptor
    public Result login(@RequestBody Map<String, String> loginMap) {
        String mobile = loginMap.get("username");
        String password = loginMap.get("password");
        //登录失败
        if (mobile == null || !password.equals("123456")) {
            return new Result(ResultCode.MOBILEORPASSWORDERROR);
        }
        //登录成功
        String token = jwtUtils.createJwt("1", "zhangsan", new HashMap<>());
        return new Result(ResultCode.SUCCESS, token);
    }
​
    @GetMapping("/info")
    @ResponseBody
    public Result getUserInfo(@SessionAttribute(GlobalConstant.USER_KEY) String userId) {
        //模拟获取该用户的信息
        HashMap<Object, Object> map = new HashMap<>();
        map.put("username", "admin");
        return new Result(ResultCode.SUCCESS, map);
    }
}

9.测试

启动项目,用postman去访问该项目的登录接口,模拟用户登录

 

登录成功,返回token

{
    "code": 10000,
    "data": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoiemhhbmdzYW4iLCJpYXQiOjE1Njg3ODc5NzUsImV4cCI6MTU2ODc4ODMzNX0.8tAvJgytFIKk2wwXRAsJ3IQV51SxnzXCFmmUcAPdbUI",
    "message": "操作成功!",
    "success": true
}

登录成功后,将返回的token设置到header中,去访问其它接口

 

如果Authorization值被伪造或过期:

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值