1.只使用session验证
(1)第一步:用户登陆时存储session
@ApiOperation("用户登陆")
@PostMapping("/login")
public AppResult login(HttpServletRequest request,@RequestParam("username") @ApiParam("用户名") @NonNull String username,@RequestParam("password") @ApiParam("用户密码") @NonNull String password) {//1.参数校验 -- 注解已完成//2.调用service层User user = userService.login(username, password);//3.设置sessionHttpSession session = request.getSession(true);session.setAttribute(AppConfig.USER_SESSION,user);return AppResult.success();
}
关键代码:
HttpServletRequest request
//3.设置sessionHttpSession session = request.getSession(true);session.setAttribute(AppConfig.USER_SESSION,user);//存储的是key-value
(2)第二步:访问接口时验获取session
@ApiOperation("查询用户信息")
@GetMapping("/info")
public AppResult<User> getUserInfo(HttpServletRequest request) {log.info("获取用户信息");User user = null;HttpSession session = request.getSession(false);if(session == null || session.getAttribute(AppConfig.USER_SESSION) == null) {//用户未登录return AppResult.failed(ResultCode.FAILED_FORBIDDEN);}user = (User) session.getAttribute(AppConfig.USER_SESSION);}if(user == null) {return AppResult.failed(ResultCode.FAILED_FORBIDDEN);}return AppResult.success(user);
}
关键代码:
HttpServletRequest request//参数
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(AppConfig.USER_SESSION) == null) {//用户未登录return AppResult.failed(ResultCode.FAILED_FORBIDDEN);
}
只要获取不到session或者不能从session中获取到对象,就说明用户没有登陆。
上述就是只使用session进行强制用户登陆的写法,需要在每个方法都进行判断。
2.使用拦截器强制登录和session
上述是没有引入拦截器的场景,每个部分都需要引入相同的代码,就会使得代码非常的繁琐,所以我们就可以使用Spring统一功能中的拦截器。
简单介绍拦截器:由两个部分组成
第一个部分:定义拦截器(也就是指定拦截规则,比如没有session就不允许访问);第二个部分:配置拦截器,也就是执行这些规则(比如把保安投放到某某路口等)
(1)第一步:定义拦截器
语法:实现HandlerInterceptor接口,并重写其所有方法
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler, ModelAndView modelAndView) throws Exception {log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");}@Overridepublic void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("LoginInterceptor 视图渲染完毕后执⾏,最后执⾏");}
}
其中里面有三个重写的方法,一般我们只需要重写第一个就够了,就是我们需要的拦截器
(1)preHeandle:目标方法前执行该方法(比如执行获取用户信息的方法前,会先执行该方法)。该方法返回true,后面的方法继续执行(不拦截);返回false,后面的方法中断。
(2)postHandle:目标方法执行后就会执行该方法。
(3)afterCompletion:视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图,暂不了解)
定义拦截器:基本模版
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;@Slf4j
//把对象交给Spring管理,否则不生效
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session = request.getSession(false);//2.判断是否存在session或者用户已登录if(session != null && session.getAttribute("user") != null) {//进入该方法,说明用户已登录,就不进行拦截操作return true;}//3.走到这一步,说明用户未登录,进行拦截(也可以指定跳转某一个页面)response.sendRedirect("/login.html");//设置跳转的页面,一般为登陆页面return false;//拦截并跳转}
}
上面就是配置拦截器的基本代码模版,有一个注意点:跳转的页面最好是封装成一个常量,利于后续的修改操作。
(2)第二步:配置拦截器
上面的第一步我们已经定义好了拦截规则,下面只需要配置就好(上面已经规定了哪些人员不准进入小区,接下来我们需要投放保安到小区的指定路口进行拦截)
语法:WebMvcConfigurer接口,并重写addInterceptors方法
基本模版:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class AppInterceptorConfigurer implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;//给保安们注入拦截规则@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor)//该方法是注册,也就是执行拦截规则.addPathPatterns("/**")//该方法表示拦截所有的请求.excludePathPatterns("/login");//表示该页面不进行拦截}
}
上面就是模版,一般登录页面和注册等页面我们都是不进行拦截的,要是拦截住了就死锁了。
然后对于要拦截什么页面,看大家的需求;对于拦截器+session的介绍就结束了,也就是上面的两段代码。
3.使用令牌拦截
使用令牌技术生成一个token字符串用来验证用户是否登陆
下面是关于用户登陆和验证的基本步骤:
(1)前端发送登陆请求,后端进行验证
(2)后端验证通过,返回token
(3)客户端存储token(通过前端进行操作)
(4)客户端后续发起的请求,就会把token放在请求的header中(k-v的形式,token存储在v中)
(5)后端从header中获取到token(指定前端的k,才能获取token),进行验证
这个部分前面先介绍如何在代码中使用登陆令牌,后面再介绍代码的含义
使用步骤大致分为四步:引入依赖、编写令牌工作类、发放令牌、验证令牌
(1)第一步:引入依赖
引入pom依赖,需要使用到令牌专属的类
<!-- jwt令牌 -->
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><!-- or jjwt-gson if Gson is preferred --><version>0.11.5</version><scope>runtime</scope>
</dependency>
引入后记得刷新maven
(2)第二步:编写令牌工具utils
在主目录下创建utils包,再创建JwtUtils类
//负责生成令牌和验证令牌import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Slf4j
public class JwtUtils {private static long expiration = 30 * 60 * 1000; //规定令牌有效期为30分钟private static String secretString = "gaiosfhioawjfajrawrawrawrawrawrawrawrfjarawrawrawrafawfoawjfa";private static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));//key值,数字签名/*** 生成令牌*/public static String genJwt(Map<String, Object> data) {//生产tokenString compact = Jwts.builder()//固定方法.setClaims(data)//设置数据.setExpiration(new Date(System.currentTimeMillis()+expiration))//设置token的过期时间.signWith(key)//设置数字签名.compact();//生产tokenreturn compact;}/*** 校验令牌*/public static Claims verify(String token){//生成token用的都是同一个key,所以不需要传参//获取build对象JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();Claims claims = null;try {claims = build.parseClaimsJws(token).getBody();//与token校验}catch (ExpiredJwtException e){log.error("token过期, 校验失败, token:"+ token);}catch (Exception e){log.error("token校验失败, token:"+ token);}return claims;}}
作用:这个类提供两个静态方法,第一个是负责生成令牌,第二个是负责验证令牌。
上述的生成和验证令牌属于大致模版,自己也可以进行大致的修改
(3)第三步:后端发放令牌
一般在登陆接口给用户发放令牌。
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public Result login(String userName, String password){//前面一堆验证工作,这里根据自己的业务功能写//………………UserInfo userInfo = new UserInfo();//账目密码验证完成,就可以给用户发放令牌了Map<String,Object> cliams = new HashMap<>();cliams.put("id", userInfo.getId());cliams.put("name", userInfo.getUserName());//获取对应的tokenString jwt = JwtUtils.genJwt(cliams);//返回tokenreturn Result.success(jwt);}
}
在验证完用户信息后,就可以给用户设置令牌并且返回token了,token中设置的信息一般不要设置隐私信息,比如用户密码
(4)第四步:后续验证令牌
这里直接采取统一拦截器进行拦截了,下面是关于拦截规则的代码
这一步作为纯后端程序猿来说比较困难,因为要和前端的参数进行同步。前端会把token(一个字符串)以k-v的形式放入header中,所以可以从请求中进行获取
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//验证token是否合法String userToken = request.getHeader("user_token_header");log.info("从header中获取信息, token:"+ userToken);Claims claims = JwtUtils.verify(userToken);if (claims==null){//token校验失败response.setStatus(401);
// response.getOutputStream().write();
// response.setContentType();return false;}return true;}
}
user_token_header是请求头中的key,用来获取对应的value值,也就是token