当前位置: 首页 > article >正文

深入浅出Bearer Token:解析工作原理及其在Vue、Uni-app与Java中的实现Demo

目录

  • 前言
  • 1. 基本知识
  • 2. Demo
  • 3. 实战

前言

🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF

1. 基本知识

Bearer Token是一种基于Token的认证机制,用于在HTTP请求中传递用户的身份信息

应用于RESTful API和各种Web应用中,提供了一种轻量且高效的身份验证方式

基本的作用如下:

  • 身份验证:通过Token验证用户的身份,确定其是否有权访问某个资源
  • 授权:Token中可以包含用户的权限信息,服务器可以根据Token中的信息决定用户可以进行的操作
  • 无状态认证:服务器不需要保存用户的会话状态,只需解析Token即可验证用户身份,使系统更易于扩展和管理

具体的工作原理如下:

  1. 用户登录:用户向服务器发送登录请求,提供用户名和密码等认证信息
  2. Token生成:服务器验证用户信息后,生成一个包含用户身份和权限信息的Bearer Token。 Token可以是纯字符串,也可以是经过签名的JWT(JSON Web Token)
  3. Token传输:服务器将生成的Token返回给客户端,客户端将其存储在本地(如浏览器的Local Storage或Cookie中)
  4. 请求资源:客户端在后续的HTTP请求中,将Bearer Token放在Authorization头中,发送到服务器
  5. Token验证:服务器接收到请求后,提取Bearer Token,解析其内容以验证用户身份和权限,决定是否允许请求

融入个人的一点小思考:

Uni-app部分,由于它是跨平台的框架,语法和Vue类似,我可以使用类似的逻辑来实现登录和.Token的管理。只需确保在不同设备上的本地存储方式一致即可。


在后端,使用Java开发时,我需要编写一个控制器来处理登录请求,生成Token,并将其返回给前端。同时,还需要编写过滤器或拦截器,用于检查每个请求的Authorization头,解析Token并验证。如果Token有效,则允许请求继续;否则,拒绝访问。


那么,如何辨别前端传来的Token是否有效呢?在后端,可以利用JWT的特性,比如检查Token的签名是否正确,Token是否过期,以及Token中的用户信息是否合法。也可以实现Token的黑名单机制,支持用户注销或停止某些Token的使用。


在实际项目中,还需要考虑Token的安全性,比如防止CSRF攻击、存储Token的方式(本地存储、Cookie等)、Token的有效期等。此外,还需要应对Token被泄露或被截获的风险,建议在传输中使用HTTPS协议。


经过以上的思考,我对Bearer Token有了基本的理解。它是一种基于Token的认证方式,适用于分布式系统和无状态的API设计。通过Token的生成、存储和验证,可以在没有会话管理的情况下实现用户的身份认证

~ 具体JWT的流程如下:
前端发送登录请求,包含用户名和密码
后端验证用户信息,创建Header和Payload
使用保密密钥对Header和Payload进行签名,生成完整的JWT
将JWT返回给前端,前端存储它

~ jwt验证流程:
前端在每次请求中携带JWT
后端提取JWT,验证其签名,确保未被篡改
解析Payload中的用户信息,进行权限检查

2. Demo

接下来会以Vue 以及 Uniapp对接Java的方式呈现一个Demo,主要是提供一个思路

实现Bearer Token的步骤

  1. 用户登录:客户端发送登录请求,携带用户名和密码;服务器验证用户信息,成功后生成Bearer Token;返回Token给客户端
  2. 存储Token:客户端将Token存储在安全的位置,如HTTP-only Cookie或Local Storage中;确保Token不会被恶意脚本窃取,推荐使用HTTPS传输
  3. 发送请求:客户端在后续请求中,在Authorization头添加"Bearer ";服务器接收到请求后,解析Token,验证用户身份
  4. Token验证:服务器检查Token的签名是否有效,确保Token未被篡改;验证Token是否过期,获取用户信息和权限;授权或拒绝请求

Bearer Token的过期与刷新
过期时间定义Token的存活时间,通常几分钟到几小时不等。Token过期后,用户需要重新登录以获取新的Token
刷新Token:用户在Token过期前,可以请求刷新Token,获取新的有效Token

  1. Vue实现

登录组件

<template>
  <div class="login">
    <h2>登录</h2>
    <form @submit.prevent="handleLogin">
      <div class="form-group">
        <label for="username">用户名</label>
        <input type="text" id="username" v-model="username" required>
      </div>
      <div class="form-group">
        <label for="password">密码</label>
        <input type="password" id="password" v-model="password" required>
      </div>
      <button type="submit">登录</button>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    async handleLogin() {
      try {
        const response = await this.$axios.post('/api/login', {
          username: this.username,
          password: this.password
        });
        
        const token = response.data.token;
        localStorage.setItem('authToken', token);
        this.$router.push('/dashboard');
      } catch (error) {
        console.error('登录失败:', error);
      }
    }
  }
};
</script>

请求拦截器

// main.js
import Vue from 'vue';
import axios from 'axios';

axios.interceptors.request.use(config => {
  const token = localStorage.getItem('authToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

Vue.config.productionTip = false;
new Vue({
  render: h => h(App),
}).$mount('#app');
  1. Uni-app实现

登录页面

<template>
  <view class="login">
    <h2>登录</h2>
    <form @submit="handleLogin">
      <view class="form-group">
        <label>用户名</label>
        <input type="text" v-model="username" />
      </view>
      <view class="form-group">
        <label>密码</label>
        <input type="password" v-model="password" />
      </view>
      <button form-type="submit">登录</button>
    </form>
  </view>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    async handleLogin() {
      try {
        const response = await this.$axios.post('/api/login', {
          username: this.username,
          password: this.password
        });
        
        const token = response.data.token;
        uni.setStorageSync('authToken', token);
        uni.navigateTo({ url: '/pages/dashboard/dashboard' });
      } catch (error) {
        console.error('登录失败:', error);
      }
    }
  }
};
</script>

Uni-app 请求拦截器

// app.vue
import Vue from 'vue';
import axios from 'axios';

axios.interceptors.request.use(config => {
  const token = uni.getStorageSync('authToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

Vue.config.productionTip = false;

App.mpType = 'app';

const app = new Vue({
  ...App
});

app.$mount();

四、后端实现:Java

User实体类

@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @Enumerated(EnumType.STRING)
    private Role role;
}

Role枚举

public enum Role {
    USER, ADMIN
}

JwtConfig配置

@Configuration
public class JwtConfig {
    @Value("${ jwt.secret}")
    private String secret;

    @Value("${ jwt.expiration}")
    private Long expiration;

    @Value("${ jwt.header}")
    private String header;

    public String getSecret() {
        return secret;
    }

    public Long getExpiration() {
        return expiration;
    }

    public String getHeader() {
        return header;
    }
}

JwtUtil工具类

@Component
public class JwtUtil {
    @Autowired
    private JwtConfig config;

    public String generateToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", user.getId());
        claims.put("username", user.getUsername());
        claims.put("role", user.getRole());
        claims.put("iat", new Date());
        claims.put("exp", new Date(System.currentTimeMillis() + config.getExpiration()));

        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS256, config.getSecret().getBytes())
                .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(config.getSecret().getBytes()).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public Claims getTokenBody(String token) {
        return Jwts.parser().setSigningKey(config.getSecret().getBytes()).parseClaimsJws(token).getBody();
    }
}

LoginController

@RestController
@RequestMapping("/api")
public class LoginController {
    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserRepository userRepository;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        User user = userRepository.findByUsername(loginRequest.getUsername())
                .orElseThrow(() -> new BadCredentialsException("用户不存在"));

        if (!user.getPassword().equals	loginRequest.getPassword()) {
            throw new BadCredentialsException("密码错误");
        }

        String token = jwtUtil.generateToken(user);
        return ResponseEntity.ok(new ApiResponse("登录成功", token));
    }
}

SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(), BasicAuthenticationFilter.class);
    }
}

JwtAuthenticationFilter

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            if (jwtUtil.validateToken(token)) {
                Claims claims = jwtUtil.getTokenBody(token);
                UsernamePasswordAuthenticationToken authentication = new 
                    UsernamePasswordAuthenticationToken(
                        claims.getSubject(), null, 
                        getAuthorities((List<String>) claims.get("role"))
                    );
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } else {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }

    private Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {
        return roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
}

3. 实战

实战中的Demo测试如下:

在这里插入图片描述

以下代码用于实战中的讲解,代码来源:https://gitee.com/zhijiantianya/ruoyi-vue-pro

uniapp中封装独特的request请求:

import store from '@/store'
import config from '@/config'
import { getAccessToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { toast, showConfirm, tansParams } from '@/utils/common'

let timeout = 10000
const baseUrl = config.baseUrl + config.baseApi;

const request = config => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  config.header = config.header || {}
  if (getAccessToken() && !isToken) {
    config.header['Authorization'] = 'Bearer ' + getAccessToken()
  }
  // 设置租户 TODO 芋艿:强制 1 先
  config.header['tenant-id'] = '1';
  // get请求映射params参数
  if (config.params) {
    let url = config.url + '?' + tansParams(config.params)
    url = url.slice(0, -1)
    config.url = url
  }
  return new Promise((resolve, reject) => {
    uni.request({
        method: config.method || 'get',
        timeout: config.timeout ||  timeout,
        url: config.baseUrl || baseUrl + config.url,
        data: config.data,
        // header: config.header,
        header: config.header,
        dataType: 'json'
      }).then(response => {
        let [error, res] = response
        if (error) {
          toast('后端接口连接异常')
          reject('后端接口连接异常')
          return
        }
        const code = res.data.code || 200
        const msg = errorCode[code] || res.data.msg || errorCode['default']
        if (code === 401) {
          showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then(res => {
            if (res.confirm) {
              store.dispatch('LogOut').then(res => {
                uni.reLaunch({ url: '/pages/login' })
              })
            }
          })
          reject('无效的会话,或者会话已过期,请重新登录。')
        } else if (code === 500) {
          toast(msg)
          reject('500')
        } else if (code !== 200) {
          toast(msg)
          reject(code)
        }
        resolve(res.data)
      })
      .catch(error => {
        let { message } = error
        if (message === 'Network Error') {
          message = '后端接口连接异常'
        } else if (message.includes('timeout')) {
          message = '系统接口请求超时'
        } else if (message.includes('Request failed with status code')) {
          message = '系统接口' + message.substr(message.length - 3) + '异常'
        }
        toast(message)
        reject(error)
      })
  })
}

export default request

其中token都是存放本地:

const AccessTokenKey = 'ACCESS_TOKEN'
const RefreshTokenKey = 'REFRESH_TOKEN'

// ========== Token 相关 ==========

export function getAccessToken() {
  return uni.getStorageSync(AccessTokenKey)
}

export function getRefreshToken() {
  return uni.getStorageSync(RefreshTokenKey)
}

export function setToken(token) {
  uni.setStorageSync(AccessTokenKey, token.accessToken)
  uni.setStorageSync(RefreshTokenKey, token.refreshToken)
}

export function removeToken() {
  uni.removeStorageSync(AccessTokenKey)
  uni.removeStorageSync(RefreshTokenKey)
}

后续只需要发送给后端即可

再说说前段也同理:

制作一个Jwt的格式:

import { useCache, CACHE_KEY } from '@/hooks/web/useCache'
import { TokenType } from '@/api/login/types'
import { decrypt, encrypt } from '@/utils/jsencrypt'

const { wsCache } = useCache()

const AccessTokenKey = 'ACCESS_TOKEN'
const RefreshTokenKey = 'REFRESH_TOKEN'

// 获取token
export const getAccessToken = () => {
  // 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
  return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')
}

// 刷新token
export const getRefreshToken = () => {
  return wsCache.get(RefreshTokenKey)
}

// 设置token
export const setToken = (token: TokenType) => {
  wsCache.set(RefreshTokenKey, token.refreshToken)
  wsCache.set(AccessTokenKey, token.accessToken)
}

// 删除token
export const removeToken = () => {
  wsCache.delete(AccessTokenKey)
  wsCache.delete(RefreshTokenKey)
}

/** 格式化token(jwt格式) */
export const formatToken = (token: string): string => {
  return 'Bearer ' + token
}

后端Java的token只需校验即可:

在这里插入图片描述


http://www.kler.cn/a/582003.html

相关文章:

  • Redis 设置密码(配置文件、docker容器、命令行3种场景)
  • HippoRAG 2 原理精读
  • Vue开发中计算属性与方法调用之间的区别与联系
  • LeNet-5卷积神经网络详解
  • 【沐渥科技】氮气柜日常如何维护?
  • QT与网页显示数据公式的方法
  • 企业未来 AI 应用中的工作重心
  • 在VMware上部署【Rocky Linux】保姆级
  • React 中的 useReducer Hook 是什么?何时使用?
  • 【外部链接跳转uniapp开发的App内指定页面】实现方案
  • 蓝桥杯省赛真题C++B组2024-握手问题
  • Spring Boot配置类原理、Spring Boot核心机制理解,以及实现自动装置的底层原理
  • 【Axure资料】110套优质可视化大屏模板+图表组件+科技感元件等
  • 【Javascript网页设计】个人简历网页案例
  • git报错:error: RPC failed; curl 16 Error in the HTTP2 framing layer
  • stm32week6
  • 华为OD机试-抢7游戏(Java 2024 D卷 100分)
  • ⭐算法OJ⭐经典题目分类索引(持续更新)
  • Tomcat+Servlet运行后出现404错误解决方案
  • 【华为OD机考真题】- 星际篮球争霸赛(Java)