一、场景与环境
最近需要写一下前后端分离下的登录解决方案,目前大多数都采用请求头携带 Token 的形式
1、我是名小白web工作者,每天都为自己的将来担心不已。第一次记录日常开发中的过程,如有表达不当,还请一笑而过;
2、本实例开发环境前端采用 angular框架,后端采用 springboot框架;
3、实现的目的如下:
a、前端实现登录操作(无注册功能);
b、后端接收到登录信息,生成有效期限token(后端算法生成的一段秘钥),作为结果返回给前端;
c、前端在此后的每次请求,都会携带token与后端校验;
d、在token有效时间内前端的请求响应都会成功,后端实时的更新token有效时间(暂无实现),如果token失效则返回登录页。
二、后端实现逻辑
注:部分代码参考网上各个大神的资料
整个服务端项目结构如下(登录token拦截只是在此工程下的一部分,文章结尾会贴上工程地址):
1、新增AccessToken 类 model
在model文件下新增AccessToken.java,此model 类保存校验token的信息:
/** * @param access_token token字段; * @param token_type token类型字段; * @param expires_in token 有效期字段; */ public class AccessToken { private String access_token; private String token_type; private long expires_in; public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public String getToken_type() { return token_type; } public void setToken_type(String token_type) { this.token_type = token_type; } public long getExpires_in() { return expires_in; } public void setExpires_in(long expires_in) { this.expires_in = expires_in; } }
2、新增Audience 类 model
@ConfigurationProperties(prefix = "audience") public class Audience { private String clientId; private String base64Secret; private String name; private int expiresSecond; public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getBase64Secret() { return base64Secret; } public void setBase64Secret(String base64Secret) { this.base64Secret = base64Secret; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getExpiresSecond() { return expiresSecond; } public void setExpiresSecond(int expiresSecond) { this.expiresSecond = expiresSecond; } }
@ConfigurationProperties(prefix = "audience")获取配置文件的信息(application.properties),如下:
server.port=8888 spring.profiles.active=dev server.servlet.context-path=https://www.jb51.net/movies audience.clientId=098f6bcd4621d373cade4e832627b4f6 audience.base64Secret=MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY= audience.name=xxx audience.expiresSecond=1800
配置文件定义了端口号、根路径和audience相关字段的信息,(audience也是根据网上资料命名的),audience的功能主要在第一次登录时,生成有效token,然后将token的信息存入上述AccessToken类model中,方便登录成功后校验前端携带的token信息是否正确。
3、生成以jwt包的CreateTokenUtils 工具类
下面对这个工具类的生成、功能进行说明:
a、首先在pom.xml文件中引用依赖(这和前端在package.json安装npm包性质相似)
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
b、然后再uitls文件夹下新增工具类CreateTokenUtils,代码如下 :
public class CreateTokenUtils { private static Logger logger = LoggerFactory.getLogger(CreateTokenUtils.class); /** * * @param request * @return s; * @throws Exception */ public static ReturnModel checkJWT(HttpServletRequest request,String base64Secret)throws Exception{ Boolean b = null; String auth = request.getHeader("Authorization"); if((auth != null) && (auth.length() > 4)){ String HeadStr = auth.substring(0,3).toLowerCase(); if(HeadStr.compareTo("mso") == 0){ auth = auth.substring(4,auth.length()); logger.info("claims:"+parseJWT(auth,base64Secret)); Claims claims = parseJWT(auth,base64Secret); b = claims==null?false:true; } } if(b == false){ logger.error("getUserInfoByRequest:"+ auth); return new ReturnModel(-1,b); } return new ReturnModel(0,b); } public static Claims parseJWT(String jsonWebToken, String base64Security){ try { Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security)) .parseClaimsJws(jsonWebToken).getBody(); return claims; } catch(Exception ex) { return null; } } public static String createJWT(String name,String audience, String issuer, long TTLMillis, String base64Security) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT") .claim("unique_name", name) .setIssuer(issuer) .setAudience(audience) .signWith(signatureAlgorithm, signingKey); if (TTLMillis >= 0) { long expMillis = nowMillis + TTLMillis; Date exp = new Date(expMillis); builder.setExpiration(exp).setNotBefore(now); } return builder.compact(); } }
此工具类有三个 静态方法:
checkJWT—— 此方法在后端拦截器中使用,检测前端发来的请求是否带有token值
createJWT——此方法在登陆接口中调用,首次登陆生成token值
parseJWT——此方法在checkJWT中调用,解析token值,将jwt类型的token值分解成audience模块
可以在parseJWT方法中打断点,查看Claims 对象,发现其字段存储的值与audience对象值一一对应。