前后端登录java_springboot 前后端分离开发 从零到整(三、登录以及登录状态的持续)...

今天来写一下怎么登录和维持登录状态。

相信登录验证大家都比较熟悉,在Javaweb中一般保持登录状态都会用session。但如果是前后端分离的话,session的作用就没有那么明显了。对于前后端分离的项目目前比较流行的是jwt验证。参考文章:https://blog.csdn.net/qq_27828675/article/details/80923678

其实,作为开发一整个项目来说,以我一年多开发经验来,建议大家先做个需求开发文档,把项目的业务大致构思一下。然后再统一把数据库设计好,把用到的表和字段都建好,可以增加开发速率的。

好了,废话少说,开始讲解一下怎么做登录验证过程吧。

首先,先创建token生成工具类。token可以设置过期时间,我项目中设置的是10个小时后过期。

添加依赖环境。

io.jsonwebtoken

jjwt

0.9.0

工具类。在生成token的时候我把用户邮箱注入token中,方便根据用户邮箱查询用户信息。秘钥暂时用的是后台写死,也可以用用户密码作为每一个token的秘钥。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.liao.tdoor.util;importio.jsonwebtoken.Claims;importio.jsonwebtoken.JwtBuilder;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;importjavax.crypto.spec.SecretKeySpec;importjavax.xml.bind.DatatypeConverter;importjava.security.Key;importjava.util.Date;/*** 生成token的工具类*/

public classtokenUtil {/*** 签名秘钥(唯一秘钥,可以用密码做为秘钥)*/

public static final String SECRET="admin";/*** 生成token

*@paramusername

*@return

*/

public staticString createJwtToken(String username){

String issuer="tdoor";

String subject="liao";long ttlMillis=36000000;//10个小时后过期

returncreateJwtToken(username,issuer,subject,ttlMillis);

}/*** 生成token

*@paramusername 用户名

*@paramissuer 改JWT的签发者,是否使用可以选

*@paramsubject 改JWT所面向的用户,是否使用可选

*@paramttlMillis 签发时间(有效时间,过期会报错)

*@returntoken string*/

public static String createJwtToken(String username,String issuer,String subject,longttlMillis){//签名算法,将token进行签名

SignatureAlgorithm signatureAlgorithm=SignatureAlgorithm.HS256;//生成签发时间

long nowMills=System.currentTimeMillis();

Date now=newDate(nowMills);//通过秘钥签名JWT

byte[] apiKeySecretBytes=DatatypeConverter.parseBase64Binary(SECRET);

Key signingKey=newSecretKeySpec(apiKeySecretBytes,signatureAlgorithm.getJcaName());//创建token

JwtBuilder builder=Jwts.builder().setId(username)

.setIssuedAt(now)

.signWith(signatureAlgorithm,signingKey);//添加过期时间

if(ttlMillis>=0){long expMillis=nowMills+ttlMillis;

Date exp=newDate(expMillis);

builder.setExpiration(exp);

}returnbuilder.compact();

}//验证和读取JWT的示例方法

public staticClaims parseJWT(String jwt){

Claims claims=Jwts.parser()

.setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))

.parseClaimsJws(jwt).getBody();returnclaims;

}public static voidmain(String[] args){

System.out.println(tokenUtil.createJwtToken("liao180@vip.qq.com"));

}

}

View Code

然后是用户登录验证。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.liao.tdoor.dao;importcom.liao.tdoor.model.User;importcom.liao.tdoor.model.UserSign;importorg.apache.ibatis.annotations.Insert;importorg.apache.ibatis.annotations.Select;importorg.apache.ibatis.annotations.SelectKey;importorg.apache.ibatis.annotations.Update;importorg.springframework.stereotype.Repository;importjava.util.Date;

@Repositorypublic interfaceUserDao {/*** @desc 查询改邮箱是否被注册

*@paramemail

*@return

*/@Select("select * from user where email=#{email}")publicUser isExistUser(String email);/*** 用户注册

*@paramuser*/@Insert("insert into user(id,email,password,nickname) values (#{id},#{email},#{password},#{nickname})")

@SelectKey(keyProperty= "id", resultType = String.class, before = true, statement = "select replace(uuid(), '-', '') as id from dual")public voidaddUser(User user);/*** 用户登录

*@paramemail

*@parampassword

*@return

*/@Select("select * from user where email=#{email} and password=#{password}")publicUser login(String email,String password);/*** 通过ID查询用户信息

*@paramuser_id

*@return

*/@Select("select * from user where id=#{user_id}")publicUser QueryInfoById(String user_id);

}

View Code

用户登录成功后生成token,把token返回给客户端。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.liao.tdoor.service;importcom.liao.tdoor.dao.CodeDao;importcom.liao.tdoor.dao.PostingDao;importcom.liao.tdoor.dao.UserDao;importcom.liao.tdoor.model.Posting;importcom.liao.tdoor.model.User;importcom.liao.tdoor.model.UserSign;importcom.liao.tdoor.model.VerificationCode;importcom.liao.tdoor.responseMsg.PersonalEntity;importcom.liao.tdoor.responseMsg.RespCode;importcom.liao.tdoor.responseMsg.RespEntity;importcom.liao.tdoor.util.DateUtils;importcom.liao.tdoor.util.RandomTools;importcom.liao.tdoor.util.SendEmailUtils;importcom.liao.tdoor.util.tokenUtil;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Repository;importorg.springframework.stereotype.Service;importjava.text.SimpleDateFormat;importjava.util.ArrayList;importjava.util.Date;importjava.util.List;/*** 用户服务层

*@author廖某某

* @date 2019/02/17*/@Servicepublic classUserService {

@Autowired

UserDao userDao;

@Autowired

CodeDao codeDao;

@Autowired

SendEmailUtils sendEmailUtils;

@Autowired

PostingDao pDao;private RespEntity respEntity=newRespEntity();private User user=newUser();private VerificationCode verificationCode=newVerificationCode();private PersonalEntity infoEntity=newPersonalEntity();/*** 发送验证码

*@paramemail

*@return

*/

publicRespEntity sendCode(String email){try{

String code= RandomTools.randomCode();//产生随机的验证码

User user=newUser();

user=userDao.isExistUser(email);if(user==null){

System.out.println("邮箱:"+email+"--验证码为:"+code);//修改数据库中的验证码

verificationCode=codeDao.checkCode(email);if(verificationCode!=null){

codeDao.changeCode(email,code);

}//发送邮件开始 发送验证码

sendEmailUtils.sendRegisterCode(email,code);//保存验证码信息到数据库

codeDao.saveCode(email,code,newDate());

respEntity=newRespEntity(RespCode.REGISTER_SEND);

}else{

respEntity= newRespEntity(RespCode.REGISTER_NOTS);

}

}catch(Exception e){

e.printStackTrace();

}returnrespEntity;

}/*** 注册信息提交

*@paramemail

*@paramnickName

*@parampassword

*@paramregisterCode

*@return

*/

publicRespEntity RegisterInfo(String email,String nickName,String password,String registerCode){

verificationCode=codeDao.checkCode(email);if(verificationCode!=null){if(registerCode.equals(verificationCode.getCode())){//时间校验--暂略

User user=newUser(email,password,nickName);

userDao.addUser(user);//删除验证码信息

codeDao.deleteCode(email);

respEntity=newRespEntity(RespCode.REGISTER_SUCCESS);

}else{

respEntity=newRespEntity(RespCode.CODE_EXPIRED);

}

}else{

respEntity=newRespEntity(RespCode.REGISTER_FAILED);

}returnrespEntity;

}/*** 登录验证

*@paramemail

*@parampassword

*@return

*/

publicRespEntity Login(String email,String password){

user=userDao.login(email,password);

String token="";if(user!=null){

token=tokenUtil.createJwtToken(email);

respEntity=newRespEntity(RespCode.LOGIN_SUCCESS,token);

}else{

respEntity=newRespEntity(RespCode.LOGIN_FAILED);

}returnrespEntity;

}/*** 根据旧密码更改密码

*@paramusedPassword

*@return

*/

publicRespEntity ChangePassword(String email,String usedPassword,String newPassword){

user=userDao.login(email,usedPassword);if(user==null){

respEntity=newRespEntity(RespCode.PASSWORD_FAILED);

}else{

userDao.ChangePassword(email,newPassword);

respEntity=newRespEntity(RespCode.SUCCESS);

}returnrespEntity;

}

}

View Code

controller。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.liao.tdoor.controller;importcom.liao.tdoor.annotation.CurrentUser;importcom.liao.tdoor.annotation.PassToken;importcom.liao.tdoor.annotation.UserLoginToken;importcom.liao.tdoor.model.User;importcom.liao.tdoor.responseMsg.PersonalEntity;importcom.liao.tdoor.responseMsg.RespEntity;importcom.liao.tdoor.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.Map;/***@author廖某某

* 用户控制层*/@RestControllerpublic classuserController {

@Autowired

UserService userService;private RespEntity respEntity=newRespEntity();private PersonalEntity pEntity=newPersonalEntity();

@RequestMapping("register")public RespEntity register(@RequestBody Mapmap){

String e_mail=(String)map.get("email");

String nickName=(String)map.get("nickName");

String password=(String)map.get("password");

String registerCode=(String)map.get("code");

respEntity=userService.RegisterInfo(e_mail,nickName,password,registerCode);returnrespEntity;

}

@RequestMapping("sendCode")public RespEntity sendPollCode(@RequestBody Mapmap){

String email=(String)map.get("email");

RespEntity respEntity=userService.sendCode(email);returnrespEntity;

}

@RequestMapping("/login")public RespEntity testData(@RequestBody Mapmap){

String email=(String)map.get("email");

String password=(String)map.get("password");

respEntity=userService.Login(email,password);returnrespEntity;

}

}

View Code

登录操作完成后,客户端根据token来进行请求操作。前端是ajax请求,一般在请求头部携带token,然后服务端拦截请求,获取token并进行验证,判断有无和是否过期,如果token不过期,则放行。

三个注解

4ef696a94cb8fff631f9712f79029985.png

注入当前登录用户注解

packagecom.liao.tdoor.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/*** 注入当前用户

*@author廖某某

* @date 2019/02/18

* 在Controller的方法参数中使用此注解,该方法在映射时会注入当前登录的User对象*/@Target(ElementType.PARAMETER)

@Retention(RetentionPolicy.RUNTIME)public @interfaceCurrentUser {

}

需要登录的标记注解

packagecom.liao.tdoor.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/*** 需要进行登录才能进行操作的注解

*@author廖某某

* @date 2019/02/8*/@Target({ElementType.METHOD,ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)public @interfaceUserLoginToken {boolean required() default true;

}

不需要登录的标记注解

packagecom.liao.tdoor.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/*** 跳过验证

*@author廖某某

* @date 2019/02/18*/@Target({ElementType.METHOD,ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)public @interfacePassToken {boolean required() default true;

}

创建拦截器。拦截器拦截token并解析token中的用户邮箱,查询用户信息并注入到CurrentUser 中。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.liao.tdoor.interceptor;importcom.liao.tdoor.annotation.PassToken;importcom.liao.tdoor.annotation.UserLoginToken;importcom.liao.tdoor.dao.UserDao;importcom.liao.tdoor.model.User;importcom.liao.tdoor.responseMsg.CurrentUserConstants;importcom.liao.tdoor.util.tokenUtil;importio.jsonwebtoken.Claims;importio.jsonwebtoken.ExpiredJwtException;importio.jsonwebtoken.Jwt;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.method.HandlerMethod;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ModelAndView;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.lang.reflect.Method;/*** 拦截器,拦截token

*@author廖某某

* @date 2019/02/18*/

public class AuthenticationInterceptor implementsHandlerInterceptor {

@Autowired

UserDao userDao;

@Overridepublic booleanpreHandle(HttpServletRequest httpServletRequest,

HttpServletResponse httpServletResponse,

Object object){//设置允许哪些域名应用进行ajax访问

httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");

httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE");

httpServletResponse.setHeader("Access-Control-Allow-Headers", " Origin, X-Requested-With, content-Type, Accept, Authorization");

httpServletResponse.setHeader("Access-Control-Max-Age","3600");//获取请求头的token

String token=httpServletRequest.getHeader("Authorization");//如果不是映射到方法直接通过

if(!(object instanceofHandlerMethod)){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 RuntimeException("无token,请重新登录");

}else{//获取token中的用户信息

Claims claims;try{

claims=tokenUtil.parseJWT(token);

}catch(ExpiredJwtException e){throw new RuntimeException("401,token失效");

}

String email=claims.getId();

User user=userDao.isExistUser(email);if(user==null){throw new RuntimeException("用户不存在,请重新登录");

}

httpServletRequest.setAttribute(CurrentUserConstants.CURRENT_USER,user);

}

}

}return true;

}//请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)

@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throwsException {

}//在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)

@Overridepublic voidafterCompletion(HttpServletRequest httpServletRequest,

HttpServletResponse httpServletResponse,

Object o, Exception e)throwsException{

}

}

View Code

配置拦截器

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.liao.tdoor.config;importcom.liao.tdoor.interceptor.AuthenticationInterceptor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.method.support.HandlerMethodArgumentResolver;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;importjava.util.List;/*** 配置拦截器

*@author廖某某

* @date 2019/02/18*/@Configurationpublic class InterceptorConfig extendsWebMvcConfigurerAdapter {

@Overridepublic voidaddInterceptors(InterceptorRegistry registry){//拦截所有请求,判断是否有@UserLogin注解,决定是否需要重新登录

registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");super.addInterceptors(registry);

}

@Overridepublic void addArgumentResolvers(ListargumentResolvers){

argumentResolvers.add(currentUserMethodArgumentResolver());super.addArgumentResolvers(argumentResolvers);

}

@BeanpublicCurrentUserMethodArgumentResolver currentUserMethodArgumentResolver(){return newCurrentUserMethodArgumentResolver();

}

@BeanpublicAuthenticationInterceptor authenticationInterceptor(){return newAuthenticationInterceptor();

}

}

View Code

自定义参数解析器解析token中包含的用户信息。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.liao.tdoor.config;importcom.liao.tdoor.annotation.CurrentUser;importcom.liao.tdoor.model.User;importcom.liao.tdoor.responseMsg.CurrentUserConstants;importorg.springframework.core.MethodParameter;importorg.springframework.web.bind.support.WebDataBinderFactory;importorg.springframework.web.context.request.NativeWebRequest;importorg.springframework.web.context.request.RequestAttributes;importorg.springframework.web.method.support.HandlerMethodArgumentResolver;importorg.springframework.web.method.support.ModelAndViewContainer;importorg.springframework.web.multipart.support.MissingServletRequestPartException;/*** 自定义参数解析器(解析user)

*@author廖某某

* @date 2019/02/18

* 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户*/

public class CurrentUserMethodArgumentResolver implementsHandlerMethodArgumentResolver {

@Overridepublic booleansupportsParameter(MethodParameter parameter){return parameter.getParameterType().isAssignableFrom(User.class) //判断是否能转换成User类型

&& parameter.hasParameterAnnotation(CurrentUser.class); //是否有CurrentUser注解

}

@OverridepublicObject resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,

NativeWebRequest webRequest, WebDataBinderFactory binderFactory)throwsException {

User user=(User) webRequest.getAttribute(CurrentUserConstants.CURRENT_USER, RequestAttributes.SCOPE_REQUEST);if (user != null) {returnuser;

}throw newMissingServletRequestPartException(CurrentUserConstants.CURRENT_USER);

}

}

View Code

这章就大概先说这么多吧,好像记得也就这么多了。

思想总结:用户登录传递邮箱密码(缺点:没有做到密码加密传输)到服务端验证,通过就返回token给前台,前台获取token保存到本地客户端。在HTML中我用的是localStorage保存,然后每次发起请求会根据是否需要登录操作而向后台传递token。服务端根据请求头的token,进行用户验证,验证通过就放行,不通过就返回登录失败信息返回前台,前台根据服务端返回的消息作出相应处理。

下一章说一下关于验证通过放行操作。如果这章有什么问题请大家见谅,并留言指正,O(∩_∩)O哈哈~。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值