亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定

JWT單點登錄教程:輕松入門指南

標簽:
PHP 安全 API
概述

本文详细介绍了JWT的工作原理和特点,并深入讲解了如何使用JWT实现单点登录。文章提供了从创建JWT令牌到验证JWT令牌的完整流程示例,并通过实战教程展示了如何在Spring Boot项目中实现JWT单点登录。文章涵盖了从环境准备到接口配置的各个方面,帮助读者轻松上手。

JWT简介

什么是JWT

JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在网络应用环境中安全地传输信息。JWT由三部分组成:头部(Header)、负载(Payload)和签名(Signature)。头部包含了令牌的类型,即JWT,和所使用的加密算法。负载包含了声明,例如用户的标识符、姓名、过期时间等。最后,签名使用了头部指定的算法,并将头部、负载以及一个密钥(只有服务器知道)进行加密,确保令牌的完整性和真实性。

JWT的工作原理

JWT的工作原理可以分为三个主要步骤:创建、传输和验证。

  1. 创建JWT:服务器端使用应用程序的私钥和用户信息生成一个JWT。这个过程包括创建头部、负载和签名。
  2. 传输JWT:客户端(通常是浏览器)在每次请求时,需要将JWT作为HTTP请求头中的Authorization部分传递给服务器。
  3. 验证JWT:当客户端发送请求时,服务器接收JWT并验证其有效性和签名,确保JWT未被篡改,并且是由服务器端生成的。

JWT的特点和优势

JWT的主要特点包括:

  • 无状态性:服务器无需存储JWT,每次请求都通过验证JWT来确认用户身份。
  • 安全性:通过签名确保令牌内容的安全性和完整性。
  • 可扩展性:负载部分可以自由扩展,以便添加更多的声明信息。
  • 跨域支持:由于JWT作为JSON格式,很容易被前端解析和使用,因此非常适合跨域环境。

单点登录简介

单点登录的概念

单点登录(Single Sign-On,简称 SSO)是一种身份验证机制,允许用户使用一组凭证(如用户名和密码)登录多个系统。一旦用户通过认证,他们就可以访问所有这些系统,而无需在每个系统中重新进行身份验证。

单点登录的优势

  1. 提高用户体验:用户无需多次输入用户名和密码,简化了登录过程。
  2. 安全性增强:减少密码泄露的风险,因为用户无需在每个系统中都记住不同的密码。
  3. 简化管理:对于系统管理员而言,只需维护一个用户数据库,减少了管理开销。

实现单点登录的方式

单点登录可以通过多种方式实现,例如:

  • 基于Cookie的SSO:依赖于浏览器的COOKIE机制,通过共享COOKIE来实现单点登录。
  • 基于Token的SSO:使用令牌(如JWT)进行身份验证,令牌在各个系统之间传递以验证用户身份。
  • 基于OAuth2的SSO:利用OAuth2协议,通过认证服务器为用户提供令牌,允许用户访问多个服务。

下面是一个基于Cookie的SSO实现示例:

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

public class CookieBasedSSOExample {

    public static void authenticateUser(HttpServletRequest request, HttpServletResponse response) {
        Cookie cookie = new Cookie("authToken", "uniqueAuthToken");
        cookie.setMaxAge(3600); // 设置Cookie的过期时间
        cookie.setPath("/"); // 设置Cookie的路径
        response.addCookie(cookie);
    }

    public static boolean isUserAuthenticated(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("authToken".equals(cookie.getName())) {
                    // 验证Cookie的有效性,例如检查是否已过期等
                    return true;
                }
            }
        }
        return false;
    }
}
使用JWT实现单点登录的基本步骤

创建JWT令牌

创建JWT令牌需要三个主要部分:头部(Header)、负载(Payload)和签名(Signature)。

  • 头部:包含令牌的类型和使用的加密算法。
  • 负载:包含关于用户的声明信息,如用户标识符、过期时间等。
  • 签名:使用密钥和指定的算法对头部和负载进行加密,确保令牌的安全。

下面是一个使用Java语言生成JWT令牌的示例代码,使用了io.jsonwebtoken库:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import java.security.Key;

public class JwtExample {
    public static void main(String[] args) {
        // 密钥,用于生成签名
        Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

        // 创建JWT
        Claims claims = Jwts
            .builder()
            .setSubject("John Doe") // 用户标识符
            .claim("roles", "admin") // 自定义声明
            .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 过期时间
            .signWith(key) // 使用密钥签名
            .compact();

        System.out.println("JWT Token: " + claims);
    }
}

验证JWT令牌

验证JWT令牌需要确保其未被篡改,并且是由服务器端生成的。这通常包括检查令牌的签名和声明的有效性。

下面是一个验证JWT令牌的Java示例代码:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import java.security.Key;

public class JwtExample {
    public static void main(String[] args) {
        // 密钥,用于生成签名
        Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

        // 验证JWT
        Claims claims = Jwts
            .parserBuilder()
            .setSigningKey(key) // 使用相同的密钥验证
            .build()
            .parseClaimsJws("your.jwt.token") // 要验证的JWT令牌
            .getBody();

        System.out.println("Subject: " + claims.getSubject());
        System.out.println("Roles: " + claims.get("roles"));
        System.out.println("Expiration: " + claims.getExpiration());
    }
}

使用JWT令牌进行用户身份验证和授权

在实际应用中,客户端(如浏览器)需要在每次请求时将JWT作为HTTP请求头的一部分传递给服务器。服务器端通过解析JWT并验证其有效性来确认用户身份,并根据令牌中的声明信息进行授权决策。

下面是一个验证JWT令牌的接口示例:

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JwtAuthenticationController {
    // 获取请求中的JWT令牌
    public String validateToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization").substring(7);

        try {
            // 密钥,用于生成签名
            Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

            // 验证JWT
            Claims claims = Jwts
                .parserBuilder()
                .setSigningKey(key) // 使用相同的密钥验证
                .build()
                .parseClaimsJws(token)
                .getBody();

            // 检查令牌是否已过期
            if (claims.getExpiration().before(new Date())) {
                return "Token is expired";
            }

            // 令牌有效,返回true
            return "Token is valid";
        } catch (Exception e) {
            return "Token is invalid";
        }
    }
}
实战教程:使用Spring Boot实现JWT单点登录

准备开发环境

  1. 安装Java和Maven:确保已安装Java开发工具包(JDK)和Maven构建工具。
  2. 创建Spring Boot项目:使用Spring Initializr(在线工具)或IDE插件创建一个Spring Boot项目,选择WebJPASpring Security等依赖项。
  3. 集成JWT相关库:在项目中添加io.jsonwebtokenio.jsonwebtoken.jwt依赖。

构建JWT认证模块

构建JWT认证模块包括创建用户验证服务、JWT生成和验证服务等。下面是一个简单的示例代码:

创建用户验证服务

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.stereotype.Service;

import java.util.ArrayList;

@Service
public class JwtUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if ("john".equals(username)) {
            return new User("john", "$2a$10$g95F8R210vazDFqOQAvXhO", new ArrayList<>());
        } else {
            throw new UsernameNotFoundException("User not found with username " + username);
        }
    }
}

创建JWT工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtil {
    private final static String SECRET = "SECRET_KEY";
    private final Key key = Keys.hmacShaKeyFor(SECRET.getBytes());

    public String generateToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

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

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getSubject();
    }

    public Date getExpirationDateFromToken(String token) {
        return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getExpiration();
    }
}

配置和测试JWT单点登录

配置Spring Security以支持JWT认证。首先,在Spring Security配置类中定义登录接口和JWT过滤器:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/authenticate").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

定义JWT过滤器拦截所有请求,并验证请求中的JWT令牌:

import com.example.demo.service.JwtUtil;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private UserDetailsService userDetailsService;
    private JwtUtil jwtUtil;

    public JwtAuthenticationFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
        this.userDetailsService = userDetailsService;
        this.jwtUtil = jwtUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        if (token != null && jwtUtil.validateToken(token)) {
            String username = jwtUtil.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(request, response);
    }
}

定义接口以生成JWT令牌:

import com.example.demo.service.JwtUtil;
import com.example.demo.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class JwtAuthenticationController {

    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private JwtUserDetailsService userDetailsService;

    @PostMapping("/authenticate")
    public Map<String, String> createAuthenticationToken(@RequestParam String username, @RequestParam String password) {
        Map<String, String> response = new HashMap<>();
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (!userDetails.isPasswordExpired() && userDetails.isAccountNonExpired() && userDetails.isAccountNonLocked() && userDetails.isEnabled()) {
            String jwtToken = jwtUtil.generateToken(new HashMap<>(), userDetails.getUsername());
            response.put("jwt", jwtToken);
        }
        return response;
    }

    @GetMapping("/validate")
    public String validateToken(HttpServletRequest request) {
        String token = request.getHeader("Authorization").substring(7);

        try {
            Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
            Claims claims = Jwts
                .parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();

            if (claims.getExpiration().before(new Date())) {
                return "Token is expired";
            }

            return "Token is valid";
        } catch (Exception e) {
            return "Token is invalid";
        }
    }
}

常见问题和解决方案

JWT令牌的安全性问题

  • 密钥泄漏:如果用于生成JWT令牌的密钥被泄露,任何人都可以生成有效的令牌,从而冒充用户身份。
  • 中间人攻击:在传输过程中,如果令牌未被安全地加密(如使用HTTPS),可能会被中间人截获。
  • 令牌篡改:如果令牌未被正确签名和验证,攻击者可能篡改令牌内容。

解决方案包括:

  • 使用强密钥:使用随机生成的长字符串作为密钥,定期更换密钥以减少泄露风险。
  • 使用安全通信协议:确保在传输JWT令牌时使用HTTPS协议,以防止中间人攻击。
  • 正确验证令牌:确保在验证令牌时不仅检查签名,还要检查过期时间和令牌的有效性。

JWT过期时间设置

合理设置JWT令牌的过期时间可以平衡用户体验和安全性。过短的过期时间可能导致频繁请求,而过长的过期时间可能增加安全风险。

  • 短期过期时间:对于高敏感操作,可以设置较短的过期时间(如几分钟)。
  • 长期过期时间:对于低敏感操作,可以设置较长的过期时间(如几小时或几天)。
  • 自动刷新机制:在令牌即将过期时,客户端可以自动请求新的令牌,以延长会话时间。

如何处理跨域问题

在处理跨域请求时,需要确保前端和后端之间的通信是安全的。Spring Boot可以通过配置CORS(跨域资源共享)来支持跨域请求。


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
``

通过上述配置,可以允许来自任何源的请求,并允许任何HTTP方法和头信息。这将有助于确保前端能够成功发送和接收JWT令牌。
點擊查看更多內容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優質文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優惠券免費領

立即參與 放棄機會
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號

舉報

0/150
提交
取消