保护你的应用:Spring Boot与JWT的黄金组合

简介: 保护你的应用:Spring Boot与JWT的黄金组合


前言

在网络的世界中,数据安全是应用开发的头等大事。而Spring Boot与JWT的完美结合,就像是为你的应用添加了一把坚实的安全之锁。从今天开始,让我们一同踏上这场奇妙的旅程,揭开Spring Boot整合JWT的神秘面纱。

第一:项目整合

基本配置

package test.bo.springbootjwt.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
public class JwtUtil {
    private static final String sign="XS4B#2&1!*";
    //生成token   header   payload    sign
    public static String getToken(Map<String,String> map){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.HOUR,24);//默认一天过期
        //创建jwt   builder
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        String token = builder.withExpiresAt(instance.getTime())//指定令牌过期时间
                        .sign(Algorithm.HMAC256(sign));
        return token;
    }
    //验证token
    public static DecodedJWT verify(String token){
       return JWT.require(Algorithm.HMAC256(sign)).build().verify(token);
    }
    //获取token信息方法
    public static DecodedJWT getTokerInfo(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(sign)).build().verify(token);
        return verify;
    }
}

token验证配置

package test.bo.springbootjwt.interceptors;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import test.bo.springbootjwt.entity.Result;
import test.bo.springbootjwt.util.JwtUtil;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException, ServletException {
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            System.out.println("OPTIONS请求,放行");
            return true;
        }
        response.setContentType("application/json;charset=UTF-8");
        Result result = new Result();
        //获取请求头中令牌
        String token = request.getHeader("token0001");
        System.out.println(token);
        try {
            JwtUtil.verify(token);//验证令牌
            return true;
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            result.setMsg("无效签名");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            result.setMsg("token过期");
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            result.setMsg("token算法不一致");
        }catch (Exception e){
            e.printStackTrace();
            result.setMsg("token无效");
        }
        result.setState(false);
       /* String string = new ObjectMapper().writeValueAsString(result);
        response.setContentType("application/json;charset=utf8");
        response.getWriter().println(string);*/
        return false;
    }
}

拦截器配置

package test.bo.springbootjwt.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import test.bo.springbootjwt.interceptors.JWTInterceptor;
@Configuration
@CrossOrigin
public class InterceptorConfig implements WebMvcConfigurer {
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .maxAge(36000)
                .allowedHeaders("*")
                .allowCredentials(true);
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/**")    //其他接口token验证
                .excludePathPatterns("/user/login"); //所有用户都放行
    }
}

依赖的pom坐标

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

第二:Spring Security与JWT的完美协作

Spring Security和JWT的结合在现代Web应用中是一种常见的做法,它提供了一种安全的身份验证和授权机制。以下是配置Spring Security以使用JWT进行身份验证的一般步骤:

步骤1: 引入依赖

确保项目中引入了Spring Security和JWT的相关依赖。可以使用Maven或Gradle进行引入,例如:

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version> <!-- 替换为最新版本 -->
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1' // 替换为最新版本

步骤2: 配置Spring Security

在Spring Boot应用的配置类中配置Spring Security,指定哪些路径需要进行身份验证,哪些路径不需要。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/public/**").permitAll() // 不需要身份验证的路径
                .anyRequest().authenticated()
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用Session
    }
}

步骤3: 实现JWT工具类

实现一个用于生成和解析JWT的工具类。可以使用JJWT库来简化这个过程。

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class JwtUtil {
    private static final String SECRET_KEY = "your_secret_key";
    public static String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }
    private static String createToken(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 * 10)) // Token有效期10小时
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
    public static Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
    private static Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }
    private static Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }
    private static <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }
    private static Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    }
    private static String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }
}

步骤4: 集成JWT到身份验证过滤器

创建一个自定义的过滤器,用于在Spring Security的过滤链中验证JWT,并将用户信息添加到Spring Security上下文。

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.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtRequestFilter extends UsernamePasswordAuthenticationFilter {
    private final UserDetailsService userDetailsService;
    private final JwtUtil jwtUtil;
    public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
        this.userDetailsService = userDetailsService;
        this.jwtUtil = jwtUtil;
    }
    @Override
    protected 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);
    }
}

步骤5: 配置Spring Security使用JWT过滤器

在配置类中将自定义的JwtRequestFilter添加到Spring Security的过滤器链中。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtUtil jwtUtil;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/authenticate").permitAll() // 不需要身份验证的路径
                .anyRequest().authenticated()
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用Session
        http.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
    }
    @Bean
    public JwtRequestFilter jwtRequestFilter() {
        return new JwtRequestFilter(userDetailsService, jwtUtil);
    }
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    public PasswordEncoder password
Encoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

以上是一个简单的集成Spring Security和JWT的例子,实际项目中可能需要根据具体需求进行更复杂的配置和定制。这个例子涵盖了基本的认证、授权和JWT生成、解析的流程。

第三:实际应用场景

Spring Boot整合JWT在实际应用中有许多灵活的应用场景,特别适用于前后端分离和微服务架构。以下是一些实际应用场景的示例:

1. 前后端分离应用

在前后端分离的应用中,前端和后端是独立开发和部署的,通过API进行通信。JWT可以在这种情境下提供简便的身份验证和授权机制。

  • 用户认证: 用户在前端登录,后端验证用户名和密码,如果通过,后端生成JWT并返回给前端。
  • 后续请求: 前端在每个请求的头部携带JWT,后端通过JWT验证用户身份和权限。

2. 微服务架构

在微服务架构中,每个服务都可以独立验证和授权用户,而不需要在每个服务中保存用户的状态。JWT在这种场景下提供了轻量级的、无状态的身份验证方式。

  • 用户认证: 一个服务处理用户登录请求,生成JWT,返回给客户端。
  • 微服务间通信: 客户端在与其他微服务通信时,携带JWT进行身份验证,每个微服务独立验证JWT。

3. 单点登录(SSO

JWT可以用于实现单点登录,使用户只需在系统中进行一次登录,即可访问多个关联系统。

  • 登录过程: 用户在一个系统中登录,获取JWT。
  • 访问其他系统: 用户在其他系统中携带JWT,其他系统通过验证JWT即可知晓用户身份。

4. Token刷新

JWT的过期机制使得可以轻松实现Token刷新,提高了系统的安全性。

  • 刷新流程: 在JWT即将过期时,客户端使用刷新令牌或重新提供用户凭证来获取新的JWT,而无需重新输入用户名和密码。

5. 动态权限调整

在某些场景下,用户的权限可能会在登录后发生变化。JWT中包含的信息可以灵活地调整用户的权限。

  • 权限调整: 当用户的权限发生变化时,系统可以主动撤销旧的JWT,生成包含新权限信息的JWT。

6. 无状态服务

JWT是无状态的,服务端不需要存储用户的状态信息,这对于水平扩展和微服务的部署非常有利。

  • 水平扩展: 可以轻松地将请求路由到不同的服务实例,而不需要共享用户的状态。

7. 跨域资源共享(CORS)

在前后端分离的应用中,处理跨域问题是常见的挑战。JWT可以在跨域环境中通过在响应头中包含Token来解决跨域问题。

  • CORS处理: 后端在响应头中包含JWT,前端在每次请求中都携带JWT,以解决浏览器的同源策略限制。

8. 移动应用身份验证

JWT也可以用于移动应用的身份验证,提供安全的身份验证机制,同时减少对服务器的频繁请求。

  • 移动应用: 移动应用通过安全的方式将用户凭证传递给服务器,获取JWT。
  • JWT在请求中携带: 移动应用在后续请求中携带JWT,服务端验证并处理请求。

这些场景展示了Spring Boot整合JWT在不同应用场景下的灵活应用。通过使用JWT,可以实现简单、安全且可扩展的身份验证和授权机制。

第四:使用公钥/私钥对JWT进行签名

使用公钥/私钥对JWT进行签名可以提高系统的安全性,确保只有拥有私钥的服务端才能生成有效的签名,而其他服务端或客户端则可以通过公钥验证签名的合法性。以下是生成密钥对并在Spring Boot中应用的步骤:

1. 生成密钥对

首先,你需要生成公钥/私钥对。可以使用Java的keytool工具或者使用一些开发库来生成。

使用keytool生成密钥对:
keytool -genkeypair -alias myjwtkey -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore myjwtkeystore.p12 -validity 3650

这将生成一个2048位的RSA密钥对,并存储在名为myjwtkeystore.p12的文件中。

使用开发库生成密钥对:

在Java中,你也可以使用一些开发库(例如Bouncy Castle)生成密钥对。以下是一个使用Bouncy Castle库的示例:

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
public class KeyPairGeneratorExample {
    public static void main(String[] args) throws NoSuchProviderException, NoSuchAlgorithmException {
        Security.addProvider(new BouncyCastleProvider());
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
        generator.initialize(2048); // key size
        KeyPair keyPair = generator.generateKeyPair();
        // keyPair.getPrivate() 返回私钥
        // keyPair.getPublic() 返回公钥
    }
}

2. 在Spring Boot中应用密钥对

在Spring Boot中,可以通过配置文件将密钥加载到应用中,以便在JWT的签名和验证过程中使用。

application.properties(或application.yml)中配置密钥:
# 密钥的存储类型(JKS或PKCS12等)
jwt.key-store-type=PKCS12
# 密钥库文件的路径
jwt.key-store=classpath:myjwtkeystore.p12
# 密钥库的密码
jwt.key-store-password=mypassword
# 别名
jwt.key-alias=myjwtkey
# 密钥的密码
jwt.key-password=mypassword
配置类中加载密钥:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.KeyStore;
@Configuration
public class JwtConfig {
    @Value("${jwt.key-store-type}")
    private String keyStoreType;
    @Value("${jwt.key-store}")
    private String keyStore;
    @Value("${jwt.key-store-password}")
    private String keyStorePassword;
    @Value("${jwt.key-alias}")
    private String keyAlias;
    @Value("${jwt.key-password}")
    private String keyPassword;
    @Bean
    public KeyPair keyPair() throws Exception {
        KeyStore keystore = KeyStore.getInstance(keyStoreType);
        keystore.load(getClass().getClassLoader().getResourceAsStream(keyStore), keyStorePassword.toCharArray());
        KeyPair keyPair = new KeyPair(
                keystore.getCertificate(keyAlias).getPublicKey(),
                (PrivateKey) keystore.getKey(keyAlias, keyPassword.toCharArray())
        );
        return keyPair;
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

这个配置类中的keyPair()方法通过加载密钥库,获取公钥和私钥的方式创建了一个KeyPair对象,以便在JWT的签名和验证中使用。

3. 在JWT签名和验证中使用密钥对

使用Spring Security和JWT库,可以在签发和验证JWT时使用密钥对。

生成JWT并签名:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtUtil {
    @Autowired
    private KeyPair keyPair;
    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
                .signWith(SignatureAlgorithm.RS256, keyPair.getPrivate())
                .compact();
    }
}
验证JWT签名:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JwtUtil {
    @Autowired
    private KeyPair keyPair;
    public Boolean validateToken(String token, UserDetails userDetails) {
        Claims claims = Jwts.parser()
                .setSigningKey(keyPair.getPublic())
                .parseClaimsJws(token)
                .getBody();
        return userDetails.getUsername().equals(claims.getSubject()) && !isTokenExpired(token);
    }
    private Boolean isTokenExpired(String token) {
        Date expirationDate = Jwts.parser()
                .setSigningKey(keyPair.getPublic())
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();
        return expirationDate.before(new Date());
    }
}

在这个例子中,SignatureAlgorithm.RS256指定了RSA算法,使用keyPair.getPrivate()进行签名,keyPair.getPublic()进行验证。

使用公钥/私钥对JWT进行签名和验证提供了更高的安全性,确保只有拥有私钥的服务端才能生成有效的签名,而其他服务端或客户端则可以通过公钥验证签名的合法性。

目录
打赏
0
1
0
0
48
分享
相关文章
Java也能快速搭建AI应用?一文带你玩转Spring AI可落地性
Java语言凭借其成熟的生态与解决方案,特别是通过 Spring AI 框架,正迅速成为 AI 应用开发的新选择。本文将探讨如何利用 Spring AI Alibaba 构建在线聊天 AI 应用,并实现对其性能的全面可观测性。
359 10
Spring AI Alibaba 应用框架挑战赛圆满落幕,恭喜获奖选手
第二届开放原子大赛 Spring AI Alibaba 应用框架挑战赛决赛于 2 月 23 日在北京圆满落幕。
|
26天前
|
Spring AI与DeepSeek实战一:快速打造智能对话应用
在 AI 技术蓬勃发展的今天,国产大模型DeepSeek凭借其低成本高性能的特点,成为企业智能化转型的热门选择。而Spring AI作为 Java 生态的 AI 集成框架,通过统一API、简化配置等特性,让开发者无需深入底层即可快速调用各类 AI 服务。本文将手把手教你通过spring-ai集成DeepSeek接口实现普通对话与流式对话功能,助力你的Java应用轻松接入 AI 能力!虽然通过Spring AI能够快速完成DeepSeek大模型与。
373 11
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
902 13
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
159 11
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
60 5
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
本文介绍了如何使用Spring AI Alibaba开发一个简单的AI对话应用。通过引入`spring-ai-alibaba-starter`依赖和配置API密钥,结合Spring Boot项目,只需几行代码即可实现与AI模型的交互。具体步骤包括创建Spring Boot项目、编写Controller处理对话请求以及前端页面展示对话内容。此外,文章还介绍了如何通过添加对话记忆功能,使AI能够理解上下文并进行连贯对话。最后,总结了Spring AI为Java开发者带来的便利,简化了AI应用的开发流程。
2634 0
Spring MVC:深入理解与应用实践
Spring MVC是Spring框架提供的一个用于构建Web应用程序的Model-View-Controller(MVC)实现。它通过分离业务逻辑、数据、显示来组织代码,使得Web应用程序的开发变得更加简洁和高效。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring MVC,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
178 2

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等