spring boot+JWT实现前后端分离快速开发

spring boot+JWT实现前后端分离快速开发
直接步入正题
首先看pom.xml,主要是spring boot的依赖包以及jwt的依赖包。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.steps.interest</groupId>
    <artifactId>steps-background</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </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>

    <dependencies>
        <!-- spring boot start -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </dependency>
        <!-- spring boot end -->

        <!-- mysql start -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.23</version>
        </dependency>
        <!-- mysql end -->

        <!-- mybatis start -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
        <!-- mybatis end -->

        <!-- AOP start -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- AOP end -->

        <!-- lombok start -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- lombok end -->

        <!-- commons-lang3 start -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <!-- commons-lang3 end -->

        <!-- alibaba druid start -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.11</version>
        </dependency>
        <!-- alibaba druid end -->

        <!-- java-jwt start -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!-- java-jwt end -->

        <!-- javax.servlet-api start -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- javax.servlet-api end -->

        <!-- fastjson start -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <!-- fastjson end -->

        <!-- cache start -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.6</version>
        </dependency>
        <!-- cache end -->
    </dependencies>

    <build>
        <finalName>api-interface</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.5.RELEASE</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
                <!--<configuration>
                    <mainClass>com.pcdd.open.OpenApplication</mainClass>
                </configuration>-->
            </plugin>
            <!-- mybatis generator 自动生成代码插件 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
                    <overwrite>false</overwrite>
                    <verbose>false</verbose>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

自定义注解PassToken与UserLoginToken

/**
 * 用来跳过验证的PassToken
 * @author hxin
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
/**
 * 需要登录才能进行操作的注解UserLoginToken
 * @author hxin
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

自定义异常枚举

/**
 * 结果编码枚举
 * @author hxin
 */
public enum ResultCodeEnum {
    SUCCESS(1,"成功"),
    FAILURE(-1,"失败"),
    ISAUTH(-10000, "token校验不通过")
    ;

    private Integer code;
    private String message;

    ResultCodeEnum(Integer code, String msg) {
        this.code = code;
        this.message = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

创建token拦截器的配置,InterceptorConfig

/***
 * Token拦截器
 * @author hxin
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    // 拦截所有请求
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void addCorsMappings(CorsRegistry arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void addFormatters(FormatterRegistry arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void addViewControllers(ViewControllerRegistry arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void configureAsyncSupport(AsyncSupportConfigurer arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void configureContentNegotiation(ContentNegotiationConfigurer arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void configurePathMatch(PathMatchConfigurer arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void configureViewResolvers(ViewResolverRegistry arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public void extendMessageConverters(List<HttpMessageConverter<?>> arg0) {
		// TODO Auto-generated method stub
		
	}
	@Override
	public MessageCodesResolver getMessageCodesResolver() {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public Validator getValidator() {
		// TODO Auto-generated method stub
		return null;
	}
}

接着就是重中之重,JWT的拦截器,前端需要在head中入token,博主以"X-Token"命名
token的生成规则是用userId+loginTime,也就是用户id+用户登录时间
这里用到了cache缓存来存放用户信息

/**
 * 拦截器
 * @author hxin
 */
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Autowired
    private AdminInfoDao adminInfoDao;

    /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller
     * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
     * @param httpServletRequest
     * @param httpServletResponse
     * @param object
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("X-Token");// 从 http 请求头中取出 token
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 执行认证
                if (token == null) {
                    throw new TokenAuthorException("无token,请重新登录");
                }
                // 获取 token 中的 user id
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new TokenAuthorException("无效token,请重新登录");
                }
                Date loginTime = (Date) CacheUtil.get(userId);
                boolean flag = false;
                if (loginTime == null) {
                    AdminInfo adminInfo = adminInfoDao.queryById(Integer.valueOf(userId));
                    if (adminInfo == null) {
                        throw new TokenAuthorException("无效token,请重新登录");
                    }
                    loginTime = adminInfo.getLoginTime();
                    flag = true;
                }
                try {
                    StringBuilder str = new StringBuilder();
                    str.append(userId);
                    str.append(loginTime);
                    // 验证 token
                    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(str.toString())).build();
                    jwtVerifier.verify(token);
                    if (flag) {
                        CacheUtil.put(userId, loginTime);
                    }
                } catch (JWTVerificationException e) {
                    throw new TokenAuthorException("无效token,请重新登录");
                }
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

生成token的方法

/**
 * Server 用于下发Token
 * @author hxin
 */
@Service("TokenService")
public class TokenService {
    /**
     * 生成token,无限时间
     * @param user
     * @return
     */
    public String getToken(AdminInfo user) {
        String token = "";
        StringBuilder str = new StringBuilder();
        str.append(user.getId());
        str.append(user.getLoginTime());
        return JWT.create().withAudience(String.valueOf(user.getId())).sign(Algorithm.HMAC256(str.toString()));
    }
}

登录接口

/**
 * 管理员登录
 * @author hxin
 */
@RestController
public class LoginController {

    @Autowired
    private LoginService loginService;
    @Autowired
    private TokenService tokenService;

    /**
     * 登录接口
     * @param account
     * @param password
     * @return
     * @throws Exception
     */
    @RequestMapping("/login")
    public BaseResult<JSONObject> login(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception {
        //1.定义返回的json
        JSONObject json = new JSONObject();
        //2.校验参数必填
        boolean isNull = new EmptyCheckUtils()
                .addObject(account)//用户名
                .addObject(password)//密码
                .empty();
        if (isNull) {
            throw new ApiException("用户名和密码必填");
        }
        //3.业务
        AdminInfo adminInfo = loginService.login(account, password);
        if (null != adminInfo) {
            //禁用
            if (StatusCommonEnum.OFF.getStatus() == adminInfo.getStatus()) {
                throw new ApiException("该管理员已被禁用");
            }
            //登录成功
            String token = tokenService.getToken(adminInfo);
            CacheUtil.put(adminInfo.getId() + "", adminInfo.getLoginTime());
            json.put("admin", adminInfo);
            json.put("token", token);
            return new BaseResult<>(ResultCodeEnum.SUCCESS.getCode(), json, true, "");
        } else {
            throw new ApiException("账号或者密码错误");
        }
    }
}

使用postman测试登录接口,拿到token
利用postman测试登录接口,拿到token
在新增管理员的方法上加上自定义注解@UserLoginToken

/**
 * 管理员
 * @author hxin
 */
@RequestMapping("admin")
@RestController
public class AdminController {

    @Autowired
    private AdminService adminService;
    @Autowired
    private HttpServletRequest request;

    /**
     * 添加管理员
     * @param
     * @return
     * @throws SQLException
     */
    @UserLoginToken
    @RequestMapping("insert")
    public BaseResult<?> insertAdmin(@RequestParam("account") String account, @RequestParam("password") String password,
                                     @RequestParam("roleId") String roleId) throws Exception {
        String token = request.getHeader("X-Token");
        //当前用户id作为parentId
        String parentId = TokenUtils.getTokenUserId(token);
        return new BaseResult(ResultCodeEnum.SUCCESS.getCode(), adminService.insert(account, password, roleId, parentId), true, "");
    }
}

使用postman测试新增管理员接口,这里测试时未往headers加入token(token错了也是一样)
未往headers加入token
使用正确的token则是这样的
往headers加入正确的token
如此,JWT的用法就是这样,欢迎补充~
【原创】未经许可,禁止转载

猜你喜欢

转载自blog.csdn.net/qq_31283333/article/details/103526457