企业的网站开发费用如何入账/北京效果好的网站推广

企业的网站开发费用如何入账,北京效果好的网站推广,手机之家,南沙区网站建设前言: 在现代的微服务架构中,用户鉴权和访问控制是非常重要的一部分。Spring Security 是 Spring 生态中用于处理安全性的强大框架,而 JWT(JSON Web Token)则是一种轻量级的、自包含的令牌机制,广泛用于分…

前言:

在现代的微服务架构中,用户鉴权和访问控制是非常重要的一部分。Spring Security 是 Spring 生态中用于处理安全性的强大框架,而 JWT(JSON Web Token)则是一种轻量级的、自包含的令牌机制,广泛用于分布式系统中的用户身份验证和信息交换。

本章实现了一个门槛极低的Spring Security+JWT实现用户鉴权访问与token刷新demo项目。具体效果可看测试部分内容。

只需要创建一个spring-boot项目,导入下文pom依赖以及项目结构如下,将各类的内容粘贴即可。(不需要nacos、数据库等配置,也不需要动yml配置文件。且用ai生成了html网页,减去了用postman测试接口的麻烦)。

也可直接选择下载项目源码,链接如下:

wlf728050719/SpringCloudPro6-1https://github.com/wlf728050719/SpringCloudPro6-1

以及本专栏会持续更新微服务项目,每一章的项目都会基于前一章项目进行功能的完善,欢迎小伙伴们关注!同时如果只是对单章感兴趣也不用从头看,只需下载前一章项目即可,每一章都会有前置项目准备部分,跟着操作就能实现上一章的最终效果,当然如果是一直跟着做可以直接跳过这一部分。专栏目录链接如下,其中Base篇为基础微服务搭建,Pro篇为复杂模块实现。

从零搭建微服务项目(全)-CSDN博客https://blog.csdn.net/wlf2030/article/details/145799620​​​​​​


依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.3</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>cn.bit</groupId><artifactId>Pro6_1</artifactId><version>0.0.1-SNAPSHOT</version><name>Pro6_1</name><description>Pro6_1</description><properties><java.version>17</java.version></properties><dependencies><!-- Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- OAuth2 Authorization Server (Spring Boot 3.x 推荐) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

核心:

工具类:

SaltUtil,用于生成随机盐。(不过由于本章没有将用户账号密码等信息存放在数据库,在代码中写死用户信息,所以这个工具类实际没有作用)。

package cn.bit.pro6_1.core.util;import java.security.SecureRandom;
import java.util.Base64;/*** 盐值工具类* @author muze*/
public class SaltUtil {/*** 生成盐值* @return 盐值*/public static String generateSalt() {// 声明并初始化长度为16的字节数组,用于存储随机生成的盐值byte[] saltBytes = new byte[16];// 创建SecureRandom实例,用于生成强随机数SecureRandom secureRandom = new SecureRandom();// 将随机生成的盐值填充到字节数组secureRandom.nextBytes(saltBytes);// 将字节数组编码为Base64格式的字符串后返回return Base64.getEncoder().encodeToString(saltBytes);}
}

JwtUtil,用于生成和验证token。(密钥为了不写配置文件就直接写代码里了,以及设置access token和refresh token失效时间为10s和20s方便测试)

package cn.bit.pro6_1.core.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;@Component
public class JwtUtil {private String secret = "wlf18086270070";private final Long accessTokenExpiration = 10L; // 1 小时private final Long refreshTokenExpiration = 20L; // 7 天public String generateAccessToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>();return createToken(claims, userDetails.getUsername(), accessTokenExpiration);}public String generateRefreshToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>();return createToken(claims, userDetails.getUsername(), refreshTokenExpiration);}private String createToken(Map<String, Object> claims, String subject, Long expiration) {return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())).setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)).signWith(SignatureAlgorithm.HS256, secret).compact();}public Boolean validateToken(String token, UserDetails userDetails) {final String username = extractUsername(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}public Date extractExpiration(String token) {return extractClaim(token, Claims::getExpiration);}private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {final Claims claims = extractAllClaims(token);return claimsResolver.apply(claims);}private Claims extractAllClaims(String token) {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}private Boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}public Date getAccessTokenExpiration() {return new Date(System.currentTimeMillis() + accessTokenExpiration * 1000);}public Date getRefreshTokenExpiration() {return new Date(System.currentTimeMillis() + refreshTokenExpiration * 1000);}
}

SecurityUtils,方便全局接口获取请求的用户信息。

package cn.bit.pro6_1.core.util;import lombok.experimental.UtilityClass;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;/*** 安全工具类** @author L.cm*/
@UtilityClass
public class SecurityUtils {/*** 获取Authentication*/public Authentication getAuthentication() {return SecurityContextHolder.getContext().getAuthentication();}/*** 获取用户* @param authentication* @return HnqzUser* <p>*/public User getUser(Authentication authentication) {if (authentication == null || authentication.getPrincipal() == null) {return null;}Object principal = authentication.getPrincipal();if (principal instanceof User) {return (User) principal;}return null;}/*** 获取用户*/public User getUser() {Authentication authentication = getAuthentication();return getUser(authentication);}
}

用户加载:

UserService,模拟数据库中有admin和buyer两个用户密码分别为123456和654321

package cn.bit.pro6_1.core.service;import cn.bit.pro6_1.core.util.SaltUtil;
import cn.bit.pro6_1.pojo.UserPO;
import lombok.AllArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
@AllArgsConstructor
public class UserService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//模拟通过username通过feign拿取到了对应用户UserPO user;BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();if (username.equals("admin")) {user = new UserPO();user.setUsername(username);user.setPassword(encoder.encode("123456"));user.setRoles("ROLE_ADMIN");user.setSalt(SaltUtil.generateSalt());}else if(username.equals("buyer")){user = new UserPO();user.setUsername(username);user.setPassword(encoder.encode("654321"));user.setRoles("ROLE_BUYER");user.setSalt(SaltUtil.generateSalt());}elsethrow new UsernameNotFoundException("not found");//模拟通过role从数据库字典项中获取对应角色权限,暂不考虑多角色用户List<GrantedAuthority> authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority(user.getRoles()));//先加入用户角色//加入用户对应角色权限if(user.getRoles().contains("ROLE_ADMIN")){authorities.add(new SimpleGrantedAuthority("READ"));authorities.add(new SimpleGrantedAuthority("WRITE"));}else if(user.getRoles().contains("ROLE_BUYER")){authorities.add(new SimpleGrantedAuthority("READ"));}return new User(user.getUsername(), user.getPassword(),authorities);}
}

过滤器:

JwtRequestFilter,用户鉴权并将鉴权信息放secruity全局上下文

package cn.bit.pro6_1.core.filter;import cn.bit.pro6_1.core.util.JwtUtil;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
@AllArgsConstructor
public class JwtRequestFilter extends OncePerRequestFilter {private JwtUtil jwtUtil;private UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {final String authorizationHeader = request.getHeader("Authorization");String username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {jwt = authorizationHeader.substring(7);username = jwtUtil.extractUsername(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (jwtUtil.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}chain.doFilter(request, response);}
}

配置类:

CorsConfig,跨域请求配置。(需要设置为自己前端运行的端口号)

package cn.bit.pro6_1.core.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import java.util.List;@Configuration
public class CorsConfig {@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOrigins(List.of("http://localhost:63342")); // 明确列出允许的域名configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); // 允许的请求方法configuration.setAllowedHeaders(List.of("*")); // 允许的请求头configuration.setAllowCredentials(true); // 允许携带凭证(如 Cookie)UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration); // 对所有路径生效return source;}
}

ResourceServerConfig,资源服务器配置。配置鉴权过滤器链,以及退出登录处理逻辑。在登录认证和刷新token时不进行access token校验,其余接口均进行token校验。这里需要将jwt的过滤器放在logout的过滤器前,否则logout无法获取secruity上下文中的用户信息,报空指针错误,从而无法做后续比如清除redis中token,日志记录等操作。

package cn.bit.pro6_1.core.config;import cn.bit.pro6_1.core.filter.JwtRequestFilter;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.cors.CorsConfigurationSource;import jakarta.servlet.http.HttpServletResponse;@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class ResourceServerConfig {private final JwtRequestFilter jwtRequestFilter;private final CorsConfigurationSource corsConfigurationSource;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.cors(cors -> cors.configurationSource(corsConfigurationSource)).csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF.authorizeHttpRequests(auth -> auth.requestMatchers("/authenticate", "/refresh-token").permitAll() // 允许匿名访问.requestMatchers("/admin/**").hasRole("ADMIN") // ADMIN 角色可访问.requestMatchers("/buyer/**").hasRole("BUYER") // BUYER 角色可访问.anyRequest().authenticated() // 其他请求需要认证).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话).logout(logout -> logout.logoutUrl("/auth/logout") // 退出登录的 URL.addLogoutHandler(logoutHandler()) // 自定义退出登录处理逻辑.logoutSuccessHandler(logoutSuccessHandler()) // 退出登录成功后的处理逻辑.invalidateHttpSession(true) // 使 HTTP Session 失效.deleteCookies("JSESSIONID") // 删除指定的 Cookie).addFilterBefore(jwtRequestFilter, LogoutFilter.class); // 添加 JWT 过滤器return http.build();}@Beanpublic LogoutHandler logoutHandler() {return (request, response, authentication) -> {if (authentication != null) {// 用户已认证,执行正常的登出逻辑System.out.println("User logged out: " + authentication.getName());// 这里可以添加其他逻辑,例如记录日志、清理资源等} else {// 用户未认证,处理未登录的情况System.out.println("Logout attempt without authentication");// 可以选择记录日志或执行其他操作}};}@Beanpublic LogoutSuccessHandler logoutSuccessHandler() {return (request, response, authentication) -> {// 退出登录成功后的逻辑,例如返回 JSON 响应response.setStatus(HttpServletResponse.SC_OK);response.getWriter().write("Logout successful");};}
}

Pojo:

封装登录请求和响应,以及用户实体类

package cn.bit.pro6_1.pojo;import lombok.Data;@Data
public class LoginRequest {private String username;private String password;
}
package cn.bit.pro6_1.pojo;import lombok.Data;import java.util.Date;@Data
public class LoginResponse {private String accessToken;private String refreshToken;private Date accessTokenExpires;private Date refreshTokenExpires;
}
package cn.bit.pro6_1.pojo;import lombok.Data;@Data
public class UserPO {private Integer id;private String username;private String password;private String roles;private String salt;
}

接口:

全局异常抓取

package cn.bit.pro6_1.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import io.jsonwebtoken.ExpiredJwtException;
import java.nio.file.AccessDeniedException;@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 全局异常.* @param e the e* @return R*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public String handleGlobalException(Exception e) {log.error("全局异常信息 ex={}", e.getMessage(), e);return e.getLocalizedMessage();}/*** AccessDeniedException* @param e the e* @return R*/@ExceptionHandler(AccessDeniedException.class)@ResponseStatus(HttpStatus.FORBIDDEN)public String handleAccessDeniedException(AccessDeniedException e) {log.error("拒绝授权异常信息 ex={}", e.getLocalizedMessage(),e);return e.getLocalizedMessage();}/**** @param e the e* @return R*/@ExceptionHandler(ExpiredJwtException.class)@ResponseStatus(HttpStatus.FORBIDDEN)public String handleExpiredJwtException(ExpiredJwtException e) {log.error("Token过期 ex={}", e.getLocalizedMessage(),e);return e.getLocalizedMessage();}
}

登录接口

package cn.bit.pro6_1.controller;import cn.bit.pro6_1.pojo.LoginRequest;
import cn.bit.pro6_1.pojo.LoginResponse;
import cn.bit.pro6_1.core.service.UserService;
import cn.bit.pro6_1.core.util.JwtUtil;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Date;@RestController
@RequestMapping("/authenticate")
@AllArgsConstructor
public class AuthenticationController {private final JwtUtil jwtUtil;private final UserService userService;private final PasswordEncoder passwordEncoder;@PostMappingpublic ResponseEntity<LoginResponse> createAuthenticationToken(@RequestBody LoginRequest loginRequest) {// 生成 Access Token 和 Refresh TokenUserDetails userDetails = userService.loadUserByUsername(loginRequest.getUsername());if(!passwordEncoder.matches(loginRequest.getPassword(), userDetails.getPassword())) {throw new RuntimeException("密码错误");}String accessToken = jwtUtil.generateAccessToken(userDetails);String refreshToken = jwtUtil.generateRefreshToken(userDetails);// 获取 Token 过期时间Date accessTokenExpires = jwtUtil.getAccessTokenExpiration();Date refreshTokenExpires = jwtUtil.getRefreshTokenExpiration();// 返回 Token 和过期时间LoginResponse loginResponse = new LoginResponse();loginResponse.setAccessToken(accessToken);loginResponse.setRefreshToken(refreshToken);loginResponse.setAccessTokenExpires(accessTokenExpires);loginResponse.setRefreshTokenExpires(refreshTokenExpires);return ResponseEntity.ok(loginResponse);}
}

access token刷新接口

package cn.bit.pro6_1.controller;import cn.bit.pro6_1.pojo.LoginRequest;
import cn.bit.pro6_1.pojo.LoginResponse;
import cn.bit.pro6_1.core.service.UserService;
import cn.bit.pro6_1.core.util.JwtUtil;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Date;@RestController
@RequestMapping("/authenticate")
@AllArgsConstructor
public class AuthenticationController {private final JwtUtil jwtUtil;private final UserService userService;private final PasswordEncoder passwordEncoder;@PostMappingpublic ResponseEntity<LoginResponse> createAuthenticationToken(@RequestBody LoginRequest loginRequest) {// 生成 Access Token 和 Refresh TokenUserDetails userDetails = userService.loadUserByUsername(loginRequest.getUsername());if(!passwordEncoder.matches(loginRequest.getPassword(), userDetails.getPassword())) {throw new RuntimeException("密码错误");}String accessToken = jwtUtil.generateAccessToken(userDetails);String refreshToken = jwtUtil.generateRefreshToken(userDetails);// 获取 Token 过期时间Date accessTokenExpires = jwtUtil.getAccessTokenExpiration();Date refreshTokenExpires = jwtUtil.getRefreshTokenExpiration();// 返回 Token 和过期时间LoginResponse loginResponse = new LoginResponse();loginResponse.setAccessToken(accessToken);loginResponse.setRefreshToken(refreshToken);loginResponse.setAccessTokenExpires(accessTokenExpires);loginResponse.setRefreshTokenExpires(refreshTokenExpires);return ResponseEntity.ok(loginResponse);}
}

admin

package cn.bit.pro6_1.controller;import cn.bit.pro6_1.core.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/admin")
public class AdminController {@GetMapping("/info")@PreAuthorize("hasRole('ADMIN')") // 只有 ADMIN 角色可以访问public String adminInfo() {User user = SecurityUtils.getUser();System.out.println(user.getUsername());return "This is admin info. Only ADMIN can access this.";}@GetMapping("/manage")@PreAuthorize("hasRole('ADMIN')") // 只有 ADMIN 角色可以访问public String adminManage() {return "This is admin management. Only ADMIN can access this.";}
}

buyer

package cn.bit.pro6_1.controller;import cn.bit.pro6_1.core.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/buyer")
public class BuyerController {@GetMapping("/info")@PreAuthorize("hasRole('BUYER')") // 只有 BUYER 角色可以访问public String buyerInfo() {User user = SecurityUtils.getUser();System.out.println(user.getUsername());return "This is buyer info. Only BUYER can access this.";}@GetMapping("/order")@PreAuthorize("hasRole('BUYER')") // 只有 BUYER 角色可以访问public String buyerOrder() {return "This is buyer order. Only BUYER can access this.";}
}

前端:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>权限控制测试</title><style>body {font-family: Arial, sans-serif;background-color: #f4f4f4;margin: 0;padding: 20px;}.container {max-width: 600px;margin: auto;background: #fff;padding: 20px;border-radius: 8px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);}h1, h2 {color: #333;}label {display: block;margin: 10px 0 5px;}input[type="text"],input[type="password"] {width: 100%;padding: 10px;margin-bottom: 20px;border: 1px solid #ccc;border-radius: 4px;}button {background-color: #28a745;color: white;padding: 10px;border: none;border-radius: 4px;cursor: pointer;width: 100%;}button:hover {background-color: #218838;}.result {margin-top: 20px;}.error {color: red;}.logout-button {background-color: #dc3545; /* 红色按钮 */margin-top: 10px;}.logout-button:hover {background-color: #c82333;}</style>
</head>
<body>
<div class="container"><h1>登录</h1><form id="loginForm"><label for="username">用户名:</label><input type="text" id="username" name="username" required><label for="password">密码:</label><input type="password" id="password" name="password" required><button type="submit">登录</button></form><div class="result" id="loginResult"></div><h2>Token 失效倒计时</h2><div id="accessTokenCountdown"></div><div id="refreshTokenCountdown"></div><h2>测试接口</h2><button onclick="testAdminInfo()">测试 /admin/info</button><button onclick="testBuyerInfo()">测试 /buyer/info</button><!-- 退出按钮 --><button class="logout-button" onclick="logout()">退出登录</button><div class="result" id="apiResult"></div>
</div><script>let accessToken = '';let refreshToken = '';let accessTokenExpires;let refreshTokenExpires;let accessTokenCountdownInterval;let refreshTokenCountdownInterval;document.getElementById('loginForm').addEventListener('submit', async (event) => {event.preventDefault();const username = document.getElementById('username').value;const password = document.getElementById('password').value;const response = await fetch('http://localhost:8080/authenticate', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ username, password })});if (response.ok) {const data = await response.json();accessToken = data.accessToken;refreshToken = data.refreshToken;accessTokenExpires = new Date(data.accessTokenExpires).getTime();refreshTokenExpires = new Date(data.refreshTokenExpires).getTime();document.getElementById('loginResult').innerHTML = `<p>登录成功!Access Token: ${accessToken}</p>`;startCountdown('accessTokenCountdown', accessTokenExpires, 'Access Token 将在 ');startCountdown('refreshTokenCountdown', refreshTokenExpires, 'Refresh Token 将在 ');} else {document.getElementById('loginResult').innerHTML = `<p class="error">登录失败,状态码: ${response.status}</p>`;}});function startCountdown(elementId, expirationTime, prefix) {const countdownElement = document.getElementById(elementId);const interval = setInterval(() => {const now = new Date().getTime();const distance = expirationTime - now;if (distance <= 0) {clearInterval(interval);countdownElement.innerHTML = `${prefix}已过期`;} else {const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));const seconds = Math.floor((distance % (1000 * 60)) / 1000);countdownElement.innerHTML = `${prefix}${hours} 小时 ${minutes} 分钟 ${seconds} 秒后过期`;}}, 1000);// 根据元素 ID 记录对应的计时器if (elementId === 'accessTokenCountdown') {accessTokenCountdownInterval = interval;} else if (elementId === 'refreshTokenCountdown') {refreshTokenCountdownInterval = interval;}}async function testAdminInfo() {if (!accessToken) {alert('请先登录!');return;}const response = await fetch('http://localhost:8080/admin/info', {method: 'GET',headers: {'Authorization': `Bearer ${accessToken}`}});if (response.ok) {const data = await response.text();document.getElementById('apiResult').innerHTML = `<p>响应: ${data}</p>`;} else if (response.status === 403) {await refreshAccessToken();await testAdminInfo(); // 重新尝试} else {document.getElementById('apiResult').innerHTML = `<p class="error">访问失败,状态码: ${response.status}</p>`;}}async function testBuyerInfo() {if (!accessToken) {alert('请先登录!');return;}const response = await fetch('http://localhost:8080/buyer/info', {method: 'GET',headers: {'Authorization': `Bearer ${accessToken}`}});if (response.ok) {const data = await response.text();document.getElementById('apiResult').innerHTML = `<p>响应: ${data}</p>`;} else if (response.status === 403) {await refreshAccessToken();await testBuyerInfo(); // 重新尝试} else {document.getElementById('apiResult').innerHTML = `<p class="error">访问失败,状态码: ${response.status}</p>`;}}async function refreshAccessToken() {const response = await fetch('http://localhost:8080/refresh-token', {method: 'POST',headers: {'Authorization': refreshToken}});if (response.ok) {const data = await response.json();accessToken = data.accessToken; // 更新 access tokenaccessTokenExpires = new Date(data.accessTokenExpires).getTime(); // 更新过期时间document.getElementById('loginResult').innerHTML = `<p>Access Token 刷新成功!新的 Access Token: ${accessToken}</p>`;// 更新 accessToken 的倒计时startCountdown('accessTokenCountdown', accessTokenExpires, 'Access Token 将在 ');} else if (response.status === 403) {// 清除 tokens 并提示用户重新登录accessToken = '';refreshToken = '';document.getElementById('loginResult').innerHTML = `<p class="error">刷新 Token 失败,请重新登录。</p>`;alert('请重新登录!');} else {document.getElementById('loginResult').innerHTML = `<p class="error">刷新 Token 失败,状态码: ${response.status}</p>`;}}// 退出登录逻辑async function logout() {// 调用退出登录接口const response = await fetch('http://localhost:8080/auth/logout', {method: 'POST',headers: {'Authorization': `Bearer ${accessToken}`}});if (response.ok) {// 清除本地存储的 tokensaccessToken = '';refreshToken = '';// 停止倒计时clearInterval(accessTokenCountdownInterval);clearInterval(refreshTokenCountdownInterval);// 更新页面显示document.getElementById('loginResult').innerHTML = `<p>退出登录成功!</p>`;document.getElementById('accessTokenCountdown').innerHTML = '';document.getElementById('refreshTokenCountdown').innerHTML = '';document.getElementById('apiResult').innerHTML = '';} else {document.getElementById('loginResult').innerHTML = `<p class="error">退出登录失败,状态码: ${response.status}</p>`;}}
</script>
</body>
</html>

测试:

启动服务,打开前端:

1.输入错误的账号

后端抛出用户名未找到的异常

2.输入错误密码

后端抛出密码错误异常

3.正确登录

显示两个token有效期倒计时以及access-token的值

4.访问admin接口

5.访问buyer接口

会看到access-token会不断刷新,但不会显示"This is buyer info. Only BUYER can access this."字体,看上去有点鬼畜,原因是前端写的是在收到403状态码后会以为是access-token过期而会访问fresh接口并再次执行一次接口。但实际上这个403是因为没有对应权限所导致的,这个问题无论改前端还是后端都能解决,但前端是ai生成的且我自己也不是很了解,后端也可限定不同异常的错误响应码,但正如开篇所说本章只是各基础demo所以就懒的改了。反正请求确实是拦截到了。

6.测试token刷新

在access-token过期但refresh-token未过期时测试admin,能够看到刷新成功且重新访问接口成功

fresh-token过期后则显示重新登录


最后:

auth模块在微服务项目中的重要性都不言而喻,目前只是实现了一个简单的框架,在后面几章会添加feign调用的鉴权,以及redis存放token从而同时获取有状态和无状态校验的优点,以及mysql交互获取数据库中信息等。还敬请关注!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/898524.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用HAI来打通DeepSeek的任督二脉

一、什么是HAI HAI是一款专注于AI与科学计算领域的云服务产品&#xff0c;旨在为开发者、企业及科研人员提供高效、易用的算力支持与全栈解决方案。主要使用场景为&#xff1a; AI作画&#xff0c;AI对话/写作、AI开发/测试。 二、开通HAI 选择CPU算力 16核32GB&#xff0c;这…

LINUX网络编程API原型详细解析

1. 网络体系 1.1. 简介 网络采用分而治之的方法设计&#xff0c;将网络的功能划分为不同的模块&#xff0c;以分层的形式有机组合在一起。 每层实现不同的功能&#xff0c;其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务&#xff0c;同时使用下层提供…

蓝桥杯 之 暴力回溯

文章目录 数字接龙小u的最大连续移动次数问题迷宫 在蓝桥杯中&#xff0c;十分喜欢考察对于网格的回溯的问题&#xff0c;对于这类的问题&#xff0c;常常会使用到这个DFS和BFS进行考察&#xff0c;不过无论怎么考察&#xff0c;都只是会在最基础的模本的基础上&#xff0c;根据…

微信小程序的业务域名配置(通过ingress网关的注解)

一、背景 微信小程序的业务域名配置&#xff08;通过kong网关的pre-function配置&#xff09;是依靠kong实现&#xff0c;本文将通过ingress网关实现。 而我们的服务是部署于阿里云K8S容器&#xff0c;当然内核与ingress无异。 找到k8s–>网络–>路由 二、ingress注解 …

Python数据可视化工具:六西格玛及其基础工具概览

在当今数据驱动的时代&#xff0c;数据分析和可视化工具成为了各行业优化流程、提升质量的关键手段。六西格玛&#xff08;Six Sigma&#xff09;作为一种以数据为基础、追求完美质量的管理理念&#xff0c;其实施依赖于一系列基础工具的灵活运用。而Python&#xff0c;凭借其强…

Spring MVC响应数据

handler方法分析 /*** TODO: 一个controller的方法是控制层的一个处理器,我们称为handler* TODO: handler需要使用RequestMapping/GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!* TODO: handler作用总结:* 1.接收请求参数(param,json,pathVariable,共享域等…

Python散点图(Scatter Plot):数据探索的“第一张图表”

在数据可视化领域,散点图是一种强大而灵活的工具,它能够帮助我们直观地理解和探索数据集中变量之间的关系。本文将深入探讨散点图的核心原理、应用场景以及如何使用Python进行高效绘制。 后续几篇将介绍高级技巧、复杂应用场景。 Python散点图(Scatter Plot):高阶分析、散点…

【redis】在 Spring中操作 Redis

文章目录 基础设置依赖StringRedisTemplate库的封装 运行StringList删库 SetHashZset 基础设置 依赖 需要选择这个依赖 StringRedisTemplate // 后续 redis 测试的各种方法&#xff0c;都通过这个 Controller 提供的 http 接口来触发 RestController public class MyC…

微服务》》Kubernetes (K8S) 集群 安装

关闭交换空间 # 切换 超级管理员身份 # 查看交换空间 free -h # 关闭交换空间 swapoff -a避免开启启动交换空间 # 注释swap开头的行 vim /etc/fstab关闭防火墙 # 关闭防火墙 # 因为K8S 是集群形式存在的 至少三台 一主二从 &#xff08;一个master 两个node&#xff09…

MySQL 简记

MySQL 简记 mysql中的数据存储的结构是B树 其与B树的相同点是&#xff0c;B树一个节点也可以存放多条数据&#xff0c;并且从左到右依次增大&#xff1b;不同点是&#xff0c;B树的叶子结点之间也能相互连接。那么实际上是采取利用空间换区时间的策略。 那么B树的树结构like…

springboot使用163发送自定义html格式的邮件

springboot使用163发送html格式的邮件 效果: 下面直接开始教学 注册邮箱&#xff0c;生成授权码 获取163邮箱的授权码&#xff0c;可以按照以下步骤操作&#xff1a; 登录163邮箱 打开浏览器&#xff0c;访问 163邮箱登录页面。 使用你的邮箱账号和密码登录。进入邮箱设置 登…

【Kafka】深入了解Kafka

集群的成员关系 Kafka使用Zookeeper维护集群的成员信息。 每一个broker都有一个唯一的标识&#xff0c;这个标识可以在配置文件中指定&#xff0c;也可以自动生成。当broker在启动时通过创建Zookeeper的临时节点把自己的ID注册到Zookeeper中。broker、控制器和其他一些动态系…

Hugging Face预训练GPT微调ChatGPT(微调入门!新手友好!)

Hugging Face预训练GPT微调ChatGPT&#xff08;微调入门&#xff01;新手友好&#xff01;&#xff09; 在实战中&#xff0c;⼤多数情况下都不需要从0开始训练模型&#xff0c;⽽是使⽤“⼤⼚”或者其他研究者开源的已经训练好的⼤模型。 在各种⼤模型开源库中&#xff0c;最…

Redis BitMap 用户签到

Redis Bitmap Bitmap&#xff08;位图&#xff09;是 Redis 提供的一种用于处理二进制位&#xff08;bit&#xff09;的特殊数据结构&#xff0c;它基于 String 类型&#xff0c;每个 bit 代表一个布尔值&#xff08;0 或 1&#xff09;&#xff0c;可以用于存储大规模的二值状…

Nodejs使用redis

框架&#xff1a;koa&#xff0c;通过koa-generator创建 redis: 本地搭建&#xff0c;使用默认帐号&#xff0c;安装说明地址以及默认启动设置&#xff1a;https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/ 中间件&#x…

调研报告:Hadoop 3.x Ozone 全景解析

Ozone 是 Hadoop 的分布式对象存储系统,具有易扩展和冗余存储的特点。 Ozone 不仅能存储数十亿个不同大小的对象,还支持在容器化环境(比如 Kubernetes)中运行。 Apache Spark、Hive 和 YARN 等应用无需任何修改即可使用 Ozone。Ozone 提供了 Java API、S3 接口和命令行接口…

AI学习——卷积神经网络(CNN)入门

作为人类&#xff0c;我们天生擅长“看”东西&#xff1a;一眼就能认出猫狗、分辨红绿灯、读懂朋友的表情……但计算机的“眼睛”最初是一片空白。直到卷积神经网络&#xff08;CNN&#xff09;​的出现&#xff0c;计算机才真正开始理解图像。今天&#xff0c;我们就用最通俗的…

⭐算法OJ⭐二叉树的前序遍历【树的遍历】(C++实现)Binary Tree Preorder Traversal

⭐算法OJ⭐二叉树的中序遍历【树的遍历】&#xff08;C实现&#xff09;Binary Tree Inorder Traversal Given the root of a binary tree, return the preorder traversal of its nodes’ values. Example 1: Input: root [1,null,2,3] Output: [1,2,3]Explanation: Exam…

计算机二级MS之Excel

声明&#xff1a;跟着大猫和小黑学习随便记下一些笔记供大家参考&#xff0c;二级考试之前将持续更新&#xff0c;希望大家二级都能轻轻松松过啦&#xff0c;过了二级的大神也可以在评论区留言给点建议&#xff0c;感谢大家&#xff01;&#xff01; 文章目录 考题难点&#x…

【Linux】VMware Workstation Pro 17 安装教程

目录 安装 VMware Workstation Pro 17 一、CDS Repository 获取安装包 二、网盘获取安装包 三、Broadcom官方获取安装包 后续安装过程没啥特殊要求 安装 VMware Workstation Pro 17 目前VMware Workstation pro 17已经对个人用户免费开放使用。 Broadcom官网地址&#x…