demo地址:https://github.com/luomouren/sbDemo
参考网址:https://www.jianshu.com/p/97362fdf039e (非常感谢作者~)
用SpringBoot开发后台用于移动端API接口,移动端登录一次后,返回accessToken字符串,移动端后面的交互只要传入accessToken即可验证其有效性,将accessToken转换成CurrentUser,在controller内可以直接使用操作其bean。需要做以下几点:
package com.xxx.demo.frame.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
/**
* token生成
* @author luomouren
*/
public class TokenUtils {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 签名秘钥
*/
public static final String SECRET = "guangYuan";
/**
* 生成token
* @param id 一般传入userName
* @return
*/
public static String createJwtToken(String id){
String issuer = "www.guangyuanbj.com";
String subject = "[email protected]";
long ttlMillis = System.currentTimeMillis();
return createJwtToken(id, issuer, subject, ttlMillis);
}
/**
* 生成Token
*
* @param id
* 编号
* @param issuer
* 该JWT的签发者,是否使用是可选的
* @param subject
* 该JWT所面向的用户,是否使用是可选的;
* @param ttlMillis
* 签发时间
* @return token String
*/
public static String createJwtToken(String id, String issuer, String subject, long ttlMillis) {
// 签名算法 ,将对token进行签名
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成签发时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
// 通过秘钥签名JWT
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
// Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now)
.setSubject(subject)
.setIssuer(issuer)
.signWith(signatureAlgorithm, signingKey);
// if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
// Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
// Sample method to validate and read the JWT
public static Claims parseJWT(String jwt) {
// This line will throw an exception if it is not a signed JWS (as expected)
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))
.parseClaimsJws(jwt).getBody();
return claims;
}
public static void main(String[] args) {
System.out.println(TokenUtils.createJwtToken("admin"));
}
}
pom.xml添加:
JSON Web Token Support For The JVM
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.0version>
dependency>
package com.xxx.demo.frame.init;
import com.xxx.demo.frame.annotation.LoginRequired;
import com.xxx.demo.frame.constants.CurrentUserConstants;
import com.xxx.demo.frame.util.TokenUtils;
import com.xxx.demo.models.sys.SysUser;
import com.xxx.demo.services.user.SysUserService;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @description:Token验证过滤器,判断是否已登录
* @author:@luomouren.
* @Date:2017-12-10 22:40
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
public final static String ACCESS_TOKEN = "accessToken";
@Autowired
private SysUserService userService;
/**
* 在请求处理之前进行调用(Controller方法调用之前)
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 判断接口是否需要登录
LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
// 有 @LoginRequired 注解,需要认证
if (methodAnnotation != null) {
// 判断是否存在令牌信息,如果存在,则允许登录
String accessToken = request.getParameter(ACCESS_TOKEN);
if (null == accessToken) {
throw new RuntimeException("无token,请重新登录");
}
Claims claims = TokenUtils.parseJWT(accessToken);
String userName = claims.getId();
SysUser user = userService.findByUserName(userName);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 当前登录用户@CurrentUser
request.setAttribute(CurrentUserConstants.CURRENT_USER, user);
return true;
}
return false;
}
/**
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
*
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
/**
* 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
*
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param e
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
package com.xxx.demo.frame.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 在需要登录验证的Controller的方法上使用此注解
*/
@Target({ElementType.METHOD})// 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)// 运行时有效
public @interface LoginRequired {
}
package com.xxx.demo.frame.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 在Controller的方法参数中使用此注解,该方法在映射时会注入当前登录的User对象
*/
@Target(ElementType.PARAMETER) // 可用在方法的参数上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
public @interface CurrentUser {
}
自定义解析器
package com.xxx.demo.frame.init;
import com.xxx.demo.frame.annotation.CurrentUser;
import com.xxx.demo.frame.constants.CurrentUserConstants;
import com.xxx.demo.models.sys.SysUser;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
/**
* @description:自定义解析器实现参数绑定
* 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户
* @author:@luomouren.
* @Date:2017-12-17 21:43
*/
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(SysUser.class)
&& parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
SysUser user = (SysUser) webRequest.getAttribute(CurrentUserConstants.CURRENT_USER, RequestAttributes.SCOPE_REQUEST);
if (user != null) {
return user;
}
throw new MissingServletRequestPartException(CurrentUserConstants.CURRENT_USER);
}
}
package com.xxx.demo.frame.constants;
/**
* @description:当前用户
* @author:@luomouren.
* @Date:2017-12-17 22:10
*/
public class CurrentUserConstants {
/**
* 当前用户参数名
*/
public final static String CURRENT_USER = "CurrentUser";
}
继承 WebMvcConfigurerAdapter 类,Override 相应的方法
package com.xxx.demo.frame.init;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
/**
* @author:@luomouren
* @Description:添加拦截器
* @Date: 2017-12-23 16:35
*/
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
@Override
public void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(currentUserMethodArgumentResolver());
super.addArgumentResolvers(argumentResolvers);
}
@Override
public void configureMessageConverters(List> converters) {
converters.add(fastJsonHttpMessageConverterEx());
super.configureMessageConverters(converters);
}
@Bean
public FastJsonHttpMessageConverterEx fastJsonHttpMessageConverterEx() {
return new FastJsonHttpMessageConverterEx();
}
@Bean
public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
return new CurrentUserMethodArgumentResolver();
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
@ResponseBody
@LoginRequired
@RequestMapping(value = "/modifyUserInfo")
public String modifyUserInfo(@CurrentUser SysUser user, String userId, String userName, String realName, String cellphone, String emodelId, String email, String description) {
logger.info(user.getUserName());
logger.info("registeredUser:userId:" + userId + ",userName:" + userName + ",realName:" + realName + ",cellphone:" + cellphone + ",emodelId:" + emodelId + ",email" + email + ",description:" + description);
JSONObject jsonObject = sysUserServices.modifyUserInfo(userId, userName, realName, cellphone, emodelId, email, description);
return jsonObject.toJSONString();
}
package com.xxx.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 按照官方的建议放在root目录下,这样才能扫描到Service和dao,不然还会引起,扫描不到注解的问题
* @author luomouren
*/
@SpringBootApplication
// mapper.java扫描
@MapperScan("com.xxx.demo.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}