SpringBoot:Spa---前后端分离---JWT token验权机制

Spa—前后端分离+JWT token验权机制

前言

什么是前后端分离?
目前,大家一致认同的前后端分离的例子就是SPA(Single-pageapplication),所有用到的展现数据都是后端通过异步接口(AJAX/JSONP)的方式提供的,前端只管展现。 前端:负责View和Controller层。 后端:只负责Model层,业务处理/数据等。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密

后端

后端是您无法直接在浏览器中看到的内容。它是处理信息并在后台准备的东西。

必要类:json统一返回值
JsonResult:

/**
 * json统一返回值
 */
public class JsonResult {
    boolean success;
    String message;
    Object data=new Object();

    public JsonResult() {
    }

    public JsonResult(boolean success, String message, Object data) {
        this.success = success;
        this.message = message;
        this.data = data;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

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

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "\nJsonResult{" +
                "success=" + success +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

JWT token验权机制

引入jwt token依赖
 <!-- JWT token -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

将jwt token封装成静态方法
JwtUtil

public static String maketoken(Object obj){
        Date now=new Date();    //当前时间
        long guoqi=now.getTime()+1000*120; //过期时间2分钟
        Date expire=new Date(guoqi);    //转型
        JwtBuilder builder= Jwts.builder().setId("001")
                .setIssuedAt(now)   //创建时间
                .setExpiration(expire)  //过期时间
                .claim("login",obj)    //可以存对象[Object]
                .signWith(SignatureAlgorithm.HS256,"Mr_xiao");  //设置签名  用HS256算法,设置密钥为Mr_xiao
        String token=builder.compact();
        return token;
    }

此处可模拟登陆,也可真实查询数据库验证
这里我用mysql数据库真实查询数据验证
全局配置application.properties

#数据库配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=UTF-8&&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=root
#mybatis
#mapper映射路径
mybatis.mapper-locations=classpath:mybatis/mapper/**/*.xml
mybatis.mapper-location=classpath:mybatis/mybatis-config.xml
#mapper映射扫描包路径开启别名
mybatis.type-aliases-package=com.mr_xiao.miaosha.entity
#关闭驼峰命名转换
mybatis.configuration.map-underscore-to-camel-case=false

Entity
User用户表根据数据库设计

public class User implements Serializable {
    int uid;
    String uname;
    String upassword;
    String uphone;
    Date birthday;

    public User(int uid, String uname, String upassword, String uphone, Date birthday) {
        this.uid = uid;
        this.uname = uname;
        this.upassword = upassword;
        this.uphone = uphone;
        this.birthday = birthday;
    }
    public User(int uid, String uname, Date birthday) {
        this.uid = uid;
        this.uname = uname;
        this.birthday = birthday;
    }
    public User(String uname, String upassword, String uphone) {
        this.uname = uname;
        this.upassword = upassword;
        this.uphone = uphone;
    }

    public User() {
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public String getUpassword() {
        return upassword;
    }

    public void setUpassword(String upassword) {
        this.upassword = upassword;
    }

    public String getUphone() {
        return uphone;
    }

    public void setUphone(String uphone) {
        this.uphone = uphone;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "\nUser{" +
                "uid=" + uid +
                ", uname='" + uname + '\'' +
                ", upassword='" + upassword + '\'' +
                ", uphone='" + uphone + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

Seckill商品表

/**
 * 秒杀商品
 */
public class Seckill {
    int seckill_id;
    String name;
    int number;
    String start_time;
    String end_time;
    String create_time;

    public Seckill(int seckill_id, String name, int number, String start_time, String end_time, String create_time) {
        this.seckill_id = seckill_id;
        this.name = name;
        this.number = number;
        this.start_time = start_time;
        this.end_time = end_time;
        this.create_time = create_time;
    }

    public Seckill() {
    }

    public int getSeckill_id() {
        return seckill_id;
    }

    public void setSeckill_id(int seckill_id) {
        this.seckill_id = seckill_id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getStart_time() {
        return start_time;
    }

    public void setStart_time(String start_time) {
        this.start_time = start_time;
    }

    public String getEnd_time() {
        return end_time;
    }

    public void setEnd_time(String end_time) {
        this.end_time = end_time;
    }

    public String getCreate_time() {
        return create_time;
    }

    public void setCreate_time(String create_time) {
        this.create_time = create_time;
    }

    @Override
    public String toString() {
        return "\nSeckill{" +
                "seckill_id=" + seckill_id +
                ", name='" + name + '\'' +
                ", number=" + number +
                ", start_time='" + start_time + '\'' +
                ", end_time='" + end_time + '\'' +
                ", create_time='" + create_time + '\'' +
                '}';
    }
}

Dao

@Mapper
@Repository
public interface UserDao {
    //login
    public User getUser(String uname,String upassword);
    //查商品
    public List getSeckill();
}

Service

public interface UserService {
    //login
    public User getUser(String uname, String upassword);
    //查商品
    public List getSeckill();
}

ServiceImpl

@Service
public class UserServiceImpl {
    @Autowired
    UserDao userDao;
    //login
    public User getUser(String uname, String upassword){
       User u1= userDao.getUser(uname, upassword);
        return u1;
    }
    //Seckill
    public List getSeckill(){
       List u1= userDao.getSeckill();
        return u1;
    }
}

Mapper

<mapper namespace="com.mr_xiao.miaosha.dao.UserDao">
    <select id="getUser" resultType="User">
        select *from user where uname=#{uname} and upassword=#{upassword}
    </select>
    <select id="getSeckill" resultType="Seckill">
        select *from seckill
    </select>
</mapper>

基本项目搭建好了
Controller

@RestController
public class MiaoshaController {
    @Autowired
    UserDao userDao;
    //登陆
    @RequestMapping("/login")
    public JsonResult login(String stname,String stpass){
        System.out.println("前台数据:"+stname+"pwd="+stpass);
        User u1=userDao.getUser(stname, stpass);
        System.out.println("user="+u1);
        String token=JwtUtil.maketoken(u1); //封装令牌
        if(u1!=null){
            return new JsonResult(true,"登陆成功",token);
        }else {
            return new JsonResult(false,"登陆失败",null);
        }

    }

    //主页商品列表
    @RequestMapping("/goods")
    public JsonResult goods(String stname,String stpass){
        System.out.println("前台数据:"+stname+"pwd="+stpass);
        List seckList=userDao.getSeckill();
        System.out.println("所有商品="+seckList);
        JsonResult json=new JsonResult(true,"查询成功",seckList);
        return json;
    }
}

拦截器Interceptor

/**
 * 登陆拦截
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getMethod().equals("OPTIONS")) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }

        System.out.println("进入登陆拦截器...");
        String token=request.getHeader("token");
        System.out.println("***检查令牌"+token);
        Claims claims= Jwts.parser().setSigningKey("Mr_xiao").parseClaimsJws(token).getBody();
        System.out.println(claims);
        // 出错了会报异常
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

ajax请求json数据难免会遇到跨域问题,所以要解决这个问题—>
WebConfig

/**
 * 支持跨域
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    LoginInterceptor login_lj;
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("*")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(login_lj)
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

后台就写好了,前台只需要直接拿数据匹对

前端

前端是与用户界面和交互相关的所有内容。其中包括HTML作为表示信息的语言,CSS作为表示信息的语言的语言以及JavaScript作为交互的语言。

首页index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .app{
            background-color: pink;
            height: 300px;
        }
    </style>
    <script src="js/jquery.min.js"></script>
</head>
<body>
    <h1>SPA 单页面应用程序</h1>
    <a href="#">登录页</a>
    <a href="#">主页</a>
    <hr />
    <div class="app">
        动态替换
    </div>
</body>
<script>
$(function(){
    $("a").eq(0).click(function(){
        $(".app").load("login.html");
    })
    $("a").eq(1).click(function(){
        $(".app").load("main.html");
    })
})
</script>
</html>

登录页Login.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<h1>登录页</h1>
		<div>
			UserName:<input type="text" id="stname" name="stname" /><br/>
			PassWord:<input type="password" id="pwd" name="stpass" /><br/>
			<button>Login</button>
		</div>
	</body>
	<script>
		$(function(){
			$("button").click(function(){
				var name=$("#stname").val();
				var pwd=$("#pwd").val();
				//向后台发ajax请求
				var url="http://localhost:8080/login";	//绝对路径
				var data={
					"stname":name,
					"stpass":pwd
				};
				$.post(url,data,function(json){
					console.log(json)
					//json	服务器返回值
					//true 去主页  false提示
					if(json.success==true){
						alert(json.message)
						//存token
						localStorage.setItem("mstoken",json.data);
						$(".app").load("main.html")
						$("a").eq(0).text("退出登录")
					}else{
						alert(json.message)
					}
				
				})
			});
		});
	</script>
</html>

数据页main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>我是主页</h1>
    <table border="1">
        <tr id="tr1">
            <th>商品列表</th>
        </tr>
    </table>
</body>
<script>
    $(function(){
        //onload ajax 查商品列表信息
        //取出token
        var mstoken=localStorage.getItem("mstoken");
        $.ajax({
            headers:{token: mstoken},
            url:"http://localhost:8080/goods",
            method:"post",
            data:{
                "stname":"zhouchen",
                "stpass":"66666"
            },
            success:function(data){
                alert(data.message)
                if(data.success==true){
                for(var i=0;i<data.data.length;i++){
                    $("#tr1").after("<tr><td>"+data.data[i].name+"</td></tr>")
                }
                }else{
                    alert("登陆已失效,请重新登陆")
                    $(".app").load("login.html")
                }
                
            }
        })
    })
</script>
</html>

首页效果图
首页
在未登录的情况下点击主页并不会显示数据,会被拦截器拦截
效果如下:

主页
在这里插入图片描述

前往登陆
ajax请求进行登陆验证,登陆成功
在这里插入图片描述
现在即可查询到数据
main
jwt token密钥失效后便又需重新登陆,否则查看不到数据
这样整个项目就odk了…

前后端分离的好处

  • 1、为优质产品打造精益团队. 术业有专攻,通过前后端分离,让前后端工程师只需要专注于前端或者后端的开发工作,培养前端工程师独特的技术特性,然后构建出一个全栈式的精益开发团队。
  • 2、提高工作效率,分工更加明确. 前后端分离的工作流程可以使得前端专心前端,后端关心后端,两者开发同时进行,提高工作效率,页面的增加和路由的修改也不必再去麻烦后端,开发更加灵活。
  • 3、局部性能提升. 通过前端路由的配置,我们可以实现页面的按需加载,无需一开始加载首页便加载网站的所有资源,服务器也不再需要解析前端页面,在页面交互及用户体验上有所提升。
  • 4、增强代码的可维护性.
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
JWT(JSON Web Token)是一种基于 JSON 的安全令牌,它可以在客户端和服务器之间安全传输信息。在前后端分离的项目中,使用 JWT 进行用户认证可以方便地处理用户登录和权限控制。 以下是使用 Spring BootJWT 实现前后端分离的用户认证示例: 1. 添加依赖 在 pom.xml 中添加以下依赖: ```xml <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建 JWT 工具类 创建一个 JWT 工具类,用于生成和验证 JWT。以下是一个简单的实现: ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(String username) { Map<String, Object> claims = new HashMap<>(); claims.put("sub", username); claims.put("iat", new Date()); return Jwts.builder() .setClaims(claims) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { try { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } catch (Exception e) { return null; } } public boolean validateToken(String token, String username) { String tokenUsername = getUsernameFromToken(token); return tokenUsername != null && tokenUsername.equals(username); } } ``` 3. 创建登录接口 创建一个登录接口,用于验证用户的用户名和密码,并返回 JWT: ```java @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = jwtUtils.generateToken(authentication.getName()); return ResponseEntity.ok(new JwtResponse(jwt)); } ``` 4. 创建受保护的接口 创建一个受保护的接口,需要用户在请求头中传递 JWT。在该接口中,首先验证 JWT 是否有效,然后根据用户角色返回相应的数据: ```java @GetMapping("/data") @PreAuthorize("hasAnyRole('USER', 'ADMIN')") public ResponseEntity<?> getData() { String username = SecurityContextHolder.getContext().getAuthentication().getName(); if (jwtUtils.validateToken(jwt, username)) { // 返回数据 } else { throw new BadCredentialsException("Invalid JWT"); } } ``` 5. 配置 JWT 相关属性 在 application.properties 中配置 JWT 相关属性: ``` jwt.secret=mySecret jwt.expiration=86400000 # 1 day in milliseconds ``` 以上就是使用 Spring BootJWT 实现前后端分离的用户认证的示例。需要注意的是,JWT 本身并没有提供加密功能,因此需要使用一个密钥来保证 JWT 的安全性。在示例中,密钥存储在配置文件中,实际生产环境中应该使用更加安全的方式来存储密钥。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

碰磕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值