给javaweb项目设置一个授权

给javaweb项目设置一个授权

背景

JavaWeb项目发布后希望持续可控,比如:

  1. 发布体验版本,有授权期限,过期后不可正常访问
  2. 服务部署后,只允许在部署服务器运行,更换服务器后不可正常访问
  3. 支持离线授权

实现思路

  1. 给项目颁发一个licence,包含用户信息、授权时间等信息,使用非对称加密对这些信息进行数字签名。
  2. 使用拦截器校验licence的有效性,根据情况返回授权无效、授权过期等信息。

具体实现

重要的说明:

** 以下代码实现基于springboot**

依赖hutool

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.8</version>
</dependency>

准备非对称加密证书

本例使用java自带的keytool来获取证书文件,简单记录一下keytool的使用。

keytool是个密钥和证书管理工具,位置在%JAVA_HOME%\bin\keytool.exe,直接使用即可。

keytool --help
-----------------------------------------------
Key and Certificate Management Tool

Commands:

 -certreq            Generates a certificate request
 -changealias        Changes an entry's alias
 -delete             Deletes an entry
 -exportcert         Exports certificate
 -genkeypair         Generates a key pair
 -genseckey          Generates a secret key
 -gencert            Generates certificate from a certificate request
 -importcert         Imports a certificate or a certificate chain
 -importpass         Imports a password
 -importkeystore     Imports one or all entries from another keystore
 -keypasswd          Changes the key password of an entry
 -list               Lists entries in a keystore
 -printcert          Prints the content of a certificate
 -printcertreq       Prints the content of a certificate request
 -printcrl           Prints the content of a CRL file
 -storepasswd        Changes the store password of a keystore

生成秘钥,更多参数:

-alias 产生别名
-keystore 指定密钥库的名称(就像数据库一样的证书库,可以有很多个证书,cacerts这个文件是jre自带的,你也可以使用其它文件名字,如果没有这个文件名字,它会创建这样一个)
-storepass 指定密钥库的密码 (获取keystore信息所需的密码)
-keypass 指定别名条目的密码**(私钥的密码)**
-list 显示密钥库中的证书信息
-v 显示密钥库中的证书详细信息
-export 将别名指定的证书导出到文件
-file 参数指定导出到文件的文件名
-delete 删除密钥库中某条目
-import 将已签名数字证书导入密钥库
-keypasswd 修改密钥库中指定条目口令
-dname 指定证书拥有者信息
-keyalg 指定密钥的算法
-validity 指定创建的证书有效期多少
-keysize 指定密钥长度

-dname 拥有者信息一般格式

CN 名称
OU 组织单位
O 组织
L 区域
ST 城市
C 国家

生成一个秘钥:

keytool -genkey -keystore "D:\keytools\test.keystore" -alias mytest -keyalg RSA -validity 30 -storepass spas123 -keypass kpas123 -dname "CN=libai, OU=com.tang, O=com.tang, L=xa, ST=sx, C=CN"

补充说明:

-genkey: 表示生成密钥对(公钥和私钥)

-keystore:每个 keytool 命令都有一个 -keystore 选项,用于指定 keytool 管理的密钥仓库的永久密钥仓库文件名称及其位置。如果不指定 -keystore 选项,则缺省密钥仓库将是宿主目录中(由系统属性的"user.home"决定)名为 .keystore 的文件。如果该文件并不存在,则它将被创建。

查看秘钥:

keytool -list -v -keystore test.keystore -storepass spas123

结果示例:

密钥库类型: jks
密钥库提供方: SUN

您的密钥库包含 1 个条目

别名: mytest
创建日期: 2022-7-31
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
所有者: CN=libai, OU=com.ping, O=com.ping, L=xa, ST=sx, C=CN
发布者: CN=libai, OU=com.ping, O=com.ping, L=xa, ST=sx, C=CN
序列号: 21882e41
生效时间: Sun Jul 31 15:25:36 CST 2022, 失效时间: Tue Aug 30 15:25:36 CST 2022
证书指纹:
SHA1: 3B:AA:C8:AE:13:F8:47:15:A4:F1:64:6D:DE:B7:ED:C1:15:67:CE:D3
SHA256: 5D:7C:3A:D1:93:CE:FB:81:85:AC:79:B3:83:ED:5F:01:07:11:7E:71:40:C4:FA:03:EC:E6:60:D8:AD:52:7A:7C
签名算法名称: SHA256withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3

扩展:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: B3 A9 B4 C8 BA B7 6B FA F9 88 0F 22 0C 51 73 72 …k…".Qsr
0010: B3 46 F2 89 .F…
]
]



参数说明:

-list 列出证书
-v 显示详细信息
-keystore 指定密钥库
-storepass 指定密钥库的解密密码
-rfc 以可编码方式打印证书

keytool -list -rfc -keystore ./test.keystore -storepass spas123

结果示例:

密钥库类型: jks
密钥库提供方: SUN

您的密钥库包含 1 个条目

别名: mytest
创建日期: 2022-7-31
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
-----BEGIN CERTIFICATE-----
MIIDWTCCAkGgAwIBAgIEIYguQTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJD
TjELMAkGA1UECBMCc3gxCzAJBgNVBAcTAnhhMREwDwYDVQQKEwhjb20ucGluZzER
MA8GA1UECxMIY29tLnBpbmcxDjAMBgNVBAMTBWxpYmFpMB4XDTIyMDczMTA3MjUz
NloXDTIyMDgzMDA3MjUzNlowXTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAnN4MQsw
CQYDVQQHEwJ4YTERMA8GA1UEChMIY29tLnBpbmcxETAPBgNVBAsTCGNvbS5waW5n
MQ4wDAYDVQQDEwVsaWJhaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AINpyuhbVXcDHgs6vflvG+/vvzHwWsH9j5GJU2pzT6+mOjpX3OO56CyHiRbZeTI+
FwhQ1xbptBGy2OFBbTGW1lJQHVta1y3liuV3r5aUYDSUpt/RU82XzUCmF5mzAwYc
2Xm5dDZp7PsFpJ1z7vP42nAW+Sk6LYWUsrciZhIOkNDKxqHUofomc31RlcF6Xv2a
CK3eEmp+pf8Hh+kpfcOr01qcj08jjVAARMeQPnbcDOh+S41KPM0NrXUqcWwHTRl1
Y3xIs6UON1RKS7W+P8irnCjgqCq2s9LYVrUFpbdepUVnnTcK2A/ebr9eXd+bdIR2
doGBwowjulgqmRgUBv6qd9kCAwEAAaMhMB8wHQYDVR0OBBYEFLOptMi6t2v6+YgP
IgxRc3KzRvKJMA0GCSqGSIb3DQEBCwUAA4IBAQAShoUVISzFj/yzTQFmAFzRyKaF
4m4+NzLUogk2zux/9A4uiazx/Ml1sXj8IVHF6uq4PhIu0nhL5h06fQvuFxgZSdyu
OfpvA0YlMIm3Nl1qUpt0u1XvCXYajJkKQY/zMyuuAtCXakEwhTdb7mhCJ7GEgHF9
1zguEt9CyhjrTsiASmxJ4ll6tqMsDpE17oaELk3yqwjzgI0suLNV6xmRe3zHSBe3
ibzfLWeVYFpDyHksw0fIziAYgz+9KlPMTjHy6GZcrOJlle97W1O2E13MFSqf8eLZ
p8OXJ1hiDLiKQjzqyql/1jaPcPjN2R4Ea1I4F6f5cuXXpIfLLVymfG55qeYk
-----END CERTIFICATE-----



其它命令:

# 导入证书
keytool -import -alias test1 -file ./test.crt -keystore ./test.keystore -storepass spas123
# 导出证书
keytool -export -alias mytest -keystore ./test.keystore -file ./test.crt -storepass spas123
# 修改密钥库中指定条目的密码
keytool -keypasswd -alias 需修改的别名 -keypass 旧密码 -new 新密码 -storepass keystore密码 -keystore 所在的密钥库
# 修改密钥库的密码
keytool -storepasswd -keystore ./yushan.keystore(需修改口令的keystore) -storepass 123456(原始密码) -new yushan(新密码)

生成证书文件:

keytool -exportcert -alias mytest -keystore ./test.keystore -storepass spas123 -file ./test.cer

证书文件包含公钥,将来发送给客户使用

拟定licence文件

准备一个licence授权文件,将签名 + 证书同项目一起发布,通过licence文件和证书判断权限。我们用一个JSON文件来做licence,java bean如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Licence {

    /**
     * 授权序列号
     */
    private String licenceId;

    /**
     * 供应商
     */
    private String vendor;
    /**
     * 过期时间
     */
    private Date expiration;
    
    /**
     * 服务器mac地址 保证只在指定的机器运行,缺点是生成秘钥是需要知道此地址,当然可以通过ipconfig查看,然后参数传入生成licence
     */
    private String macAddress;

    /**
     * 数字签名
     */
    private String signature;


}

如果想分模块授权,可以适当增加模块参数。

生成和校验licence

签名需要使用证书,先准备加载证书的方法(证书信息存放在yml配置文件中):

yml局部:

# 认证证书逻辑
licence:
  base-path: D://KeyStore
  key-store: ${licence.base-path}/test.keystore
  key-alias: mytest
  cert: ${licence.base-path}/test.cer
  key-store-pwd: spas123
  key-pwd: kpas123
  auth-file: ${licence.base-path}/licence.json

准备工具类加载证书:

/**
 * 证书加载工具
 */
@Component
public class KeyTools {


    // 私钥存放路径
    @Value("${licence.key-store}")
    public String PRIVATE_KEY_FILE_PATH;

    //Cer证书存放路径
    @Value("${licence.cert}")
    public String CER_FILE_PATH;

    //私钥别名
    @Value("${licence.key-alias}")
    public String PRIVATE_ALIAS;

    //获取keystore密码
    @Value("${licence.key-store-pwd}")
    public String KEYSTORE_PASSWORD;
    //获取私钥所需密码
    @Value("${licence.key-pwd}")
    public String KEY_PASSWORD;


    /**
     * 获取私钥
     * @return
     */
    public PrivateKey getPrivateKey() {

        FileInputStream is = null;
        PrivateKey privateKey = null;
        try {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            is = new FileInputStream(PRIVATE_KEY_FILE_PATH);
            keyStore.load(is, KEYSTORE_PASSWORD.toCharArray());
            privateKey = (PrivateKey) keyStore.getKey(PRIVATE_ALIAS, KEY_PASSWORD.toCharArray());

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return privateKey;
    }

    /**
     * 通过 cer证书获取公钥
     */
    public PublicKey getPublicKey(){
        PublicKey publicKey = null;
        FileInputStream in = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            in = new FileInputStream(CER_FILE_PATH);
            Certificate c = cf.generateCertificate(in);
            publicKey = c.getPublicKey();
        } catch (CertificateException | FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return publicKey;
    }

}

准备一个工具类 完成签名生成和验签

重要的约定:

将证书中的值以字典序排列,然后生成签名


import cn.hutool.core.util.CharsetUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
public class MySignUtil {

    private final static String PARAM_SIGN = "signature";
    private static final String KEY_ALGORITHM = "SHA1withRSA";
    private static Signature signature;

    static {
        try {
            signature = Signature.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取签名
     *
     * @param data
     * @param privateKey
     * @return
     */
    public static String sign(byte[] data, PrivateKey privateKey) {
        try {
            signature.initSign(privateKey);
            signature.update(data);
            return new String(Base64.getEncoder().encode(signature.sign()), CharsetUtil.UTF_8);
        } catch (InvalidKeyException | SignatureException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String sign(Object data, PrivateKey privateKey) {
        return sign(getParams(data), privateKey);
    }

    /**
     * 验签
     *
     * @param data
     * @param sign
     * @param publicKey
     * @return
     */
    public static boolean verify(byte[] data, byte[] sign, PublicKey publicKey) {
        try {
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(Base64.getDecoder().decode(sign));
        } catch (InvalidKeyException | SignatureException e) {
            e.printStackTrace();
        }
        return false;
    }

    public static boolean verify(Object data, String signature, PublicKey publicKey) {
        return verify(getParams(data), signature.getBytes(StandardCharsets.UTF_8), publicKey);
    }

    /**
     * 获取参数
     *
     * @param obj
     * @return
     */
    private static byte[] getParams(Object obj) {
        if (Objects.isNull(obj)) {
            log.error("签名获取失败, 传入对象为null");
            return null;
        }
        // 获取非sign参数值
        JSONObject js = (JSONObject) JSON.toJSON(obj);
        js.remove(PARAM_SIGN);

        Collection<Object> values = js.values();
        String params = values.stream().map(String::valueOf).sorted().collect(Collectors.joining());

        return params.getBytes(StandardCharsets.UTF_8);
    }

}

准备好内容, 转换为JSON字符串写入文件即为授权文件,生成过程。生成licence:

import org.jeecg.modules.licence.entity.Licence;

import java.io.File;

public interface ILicenceService {

    /**
     * 生成授权文件
     *
     * @param licence
     * @return
     */
    File generateLicence(Licence licence);

    /**
     * 实现签名校验
     *
     * @param licence
     * @return
     */
    boolean verifySign(Licence licence);

    /**
     * 签名校验 检验默认位置签名
     *
     * @return
     */
    boolean verifySign();

    /**
     * 从磁盘中获取licence
     *
     * @return
     */
    Licence getLicence();

    /**
     * 当前licence是否有效
     *
     * @return
     */
    boolean isEffective();

    /**
     * 清空licence缓存
     *
     * @return
     */
    boolean clearEffectiveCache();

}

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.jeecg.modules.licence.entity.Licence;
import org.jeecg.modules.licence.service.ILicenceService;
import org.jeecg.modules.licence.util.KeyTools;
import org.jeecg.modules.licence.util.MySignUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.io.File;
import java.nio.charset.StandardCharsets;

@Service
public class LicenceServiceImpl implements ILicenceService {

    @Value("${licence.auth-file}")
    private String licencePath;

    @Autowired
    private KeyTools keyTools;

    @Override
    public File generateLicence(Licence licence) {
        // 生成签名
        String sign = MySignUtil.sign(licence, keyTools.getPrivateKey());
        licence.setSignature(sign);
        // 生成licence文件
        String jsonStr = JSONUtil.toJsonStr(licence);
        File file = FileUtil.writeString(jsonStr, licencePath, CharsetUtil.UTF_8);
        return file;
    }

    @Override
    public boolean verifySign(Licence licence) {
        return MySignUtil.verify(licence, licence.getSignature(), keyTools.getPublicKey());
    }

    @Override
    public boolean verifySign() {
        Licence licence = getLicence();
        return MySignUtil.verify(licence, licence.getSignature(), keyTools.getPublicKey());
    }

    @Override
    public Licence getLicence() {
        String licenceStr = FileUtil.readString(licencePath, StandardCharsets.UTF_8);
        JSONObject jsonObject = JSONUtil.parseObj(licenceStr);
        Licence licence = jsonObject.toBean(Licence.class);
        return licence;
    }


    @Override
    // 这里增加了一个缓存, 后面会添加拦截器, 如果每个请求都重新验证一遍证书, 会带来性能上的压力
    @Cacheable(value = "licence", unless = "#result == false ")
    public boolean isEffective() {
        // 证书时间和licence时间是一致的, 如果证书过期签名校验不通过, licence肯定过期
        boolean signVerify = this.verifySign();
        if(!signVerify) {
            return false;
        }

        Licence licence = getLicence();
        if (StrUtil.isNotEmpty(licence.getMacAddress())) {
            // 校验Mac地址
            String macAddress = NetUtil.getLocalMacAddress();
            if (!StrUtil.equals(macAddress, licence.getMacAddress())) {
                return false;
            }
        }
        return true;
    }

    @Override
    @CacheEvict(value = "licence")
    public boolean clearEffectiveCache() {
        return true;
    }
}

准备一个controller测试过程


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.licence.entity.Licence;
import org.jeecg.modules.licence.service.ILicenceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 证书相关信息
 */
@Controller
@RequestMapping("test/licence")
public class LicenceController {

    @Autowired
    private ILicenceService licenceService;
	
    /**
     * 生成licence
     * @return
     */
    @RequestMapping(value = "generate", method = {RequestMethod.GET, RequestMethod.POST})
    public Result generateLicence() {
        Licence licence = new Licence();

        licence.setLicenceId(UUID.fastUUID().toString(true))
                .setExpiration(DateUtil.offsetDay(DateUtil.date(), 30))
                .setVendor("XX科技有限责任公司");
        
        // 这里直接获取了本机的网卡Mac 实际操作中可以通过参数掺入的方式
        String macAddress = NetUtil.getLocalMacAddress();
        licence.setMacAddress(macAddress);

        licenceService.generateLicence(licence);
        return Result.OK(licence);
    }
	
    /**
     * 校验licence  验签  这里证书过期或者licence被修改都会导致验签失败
     * @return
     */
    @RequestMapping(value = "verify", method = {RequestMethod.GET, RequestMethod.POST})
    public Result verifyLicence() {
        return Result.OK(licenceService.verifySign());
    }

}

添加licence拦截器

拦截所有的请求, 校验授权是否有效:


import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.licence.entity.Licence;
import org.jeecg.modules.licence.service.ILicenceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 授权校验拦截器, 校验licence是否合法
 */

@Slf4j
@Component
public class LicenceInterceptor implements HandlerInterceptor {

    @Autowired
    private ILicenceService licenceService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (licenceService.isEffective()) {
            return true;
        }else{
            Licence licence = licenceService.getLicence();
            if (licence == null) {
                output(Result.error("项目未授权"), response);
            }else{
                if (DateUtil.compare(DateUtil.date(), DateUtil.parseDate(licence.getExpiration())) >= 0) {
                    // 有授权, 但是过期了
                    output(Result.error("授权已过期"), response);
                }else{
                    output(Result.error("项目授权无效"), response);
                }
            }
        }
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }


    /**
     * 向页面返回信息
     *
     * @param result
     * @param response
     */
    public void output(Result result, HttpServletResponse response) throws IOException {
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print(JSONObject.toJSONString(result));
        writer.close();
        response.flushBuffer();
    }
}

当然记得注册一下拦截器。这样就完成了web项目授权,只有授权有效的情况下才能正确返回。

其他注意事项

  1. 向客户发布项目时,只附带licence.json和test.cer,自己留好keystore文件。
  2. 应该给用户一些友好的提示,比如在证书快过期时提醒用户及时获取授权;已过期时提醒用户是因为授权原因导致服务不可用等。
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 一个 JavaWeb 项目常用的技术包括: 1. Java Servlet 和 JSP 技术: Java Servlet 和 JSP 是构建 JavaWeb 应用程序的核心技术。 2. 数据库技术: JavaWeb 应用程序需要与数据库进行交互,常用的数据库技术包括 JDBC 和 ORM 框架(如 MyBatis 和 Hibernate)。 3. 前端技术: 前端技术主要包括 HTML、CSS、JavaScript、jQuery 等。 4. Web 服务器: 常用的 Web 服务器包括 Tomcat、Jetty 等。 5. 框架和工具: 常用的 JavaWeb 框架和工具包括 Spring、SpringMVC、Struts2 等,以及 Maven、Gradle 等构建工具。 6. 安全技术: 安全技术包括 HTTPS、SSL、加密、认证、授权等。 ### 回答2: 一个Java Web项目主要用到的技术可以分为几个方面。 首先,Java Web项目主要使用Java编程语言作为开发语言。Java具有跨平台、面向对象、易于调试和维护等优势,非常适合用于构建Web应用程序。 其次,Java Web项目通常使用Java Servlet技术。Java Servlet是一种能够接收和响应HTTP请求的小程序,它可以与Web服务器进行交互,处理用户请求,生成动态网页内容。通过Servlet技术,可以实现用户登录、数据查询、表单提交等功能。 另外,Java Web项目还会用到JavaServer Pages(JSP)技术。JSP是一种在HTML(后缀名为.jsp)文件中嵌入Java代码的技术,它可以动态地生成HTML页面。在JSP中,可以使用Java代码、标签库和EL表达式等技术,非常便于与数据库进行交互和处理动态内容。 此外,Java Web项目还需要使用Web容器来运行。常用的Web容器有Apache Tomcat、JBoss等。Web容器可以解析和执行Servlet和JSP,同时提供了许多支持Web开发的功能,如连接池、线程池、安全管理等。 另外,Java Web项目还经常用到数据库技术,如MySQL、Oracle等。通过数据库技术,可以存储和管理项目中的数据。 除了以上主要技术,Java Web项目还可能使用一些框架和工具,如Spring、Hibernate、Struts等。这些框架和工具提供了一些常用的功能和开发方法,可以加快开发速度和提高项目的可维护性。 总结起来,一个Java Web项目主要用到的技术包括Java编程语言、Java Servlet、JSP、Web容器、数据库技术以及一些框架和工具。这些技术的综合应用,可以实现一个完整的Web应用程序。 ### 回答3: 一个JavaWeb项目主要用到的技术包括以下几个方面。 首先,JavaWeb项目需要使用Java编程语言进行开发,因此需要具备Java的基础知识和面向对象编程的能力。 其次,JavaWeb项目需要使用Servlet技术来处理HTTP请求和响应,并进行业务处理。Servlet是运行在服务器端的Java程序,能够与客户端进行通信,生成动态的网页内容。 另外,JavaWeb项目中常常使用JSP(Java Server Pages)技术来实现前端的动态页面和用户界面。JSP是一种与HTML紧密结合的页面模板技术,能够嵌入Java代码,便于生成动态的网页内容。 此外,JavaWeb项目中还需要使用数据库来存储和管理数据。常用的数据库技术包括关系型数据库如MySQL和Oracle,以及非关系型数据库如MongoDB和Redis。开发者需要掌握使用Java的数据库访问技术(如JDBC或ORM框架)来操作数据库。 另外,JavaWeb项目通常涉及到与前端进行异步通信,如通过AJAX来实现局部刷新。开发者需要熟悉JavaScript、jQuery等前端开发技术,以及掌握使用Java来编写接口供前端调用的能力。 最后,JavaWeb项目还需要搭建服务器环境来部署和运行。常用的服务器技术包括Tomcat、WebLogic和WebSphere等。开发者需要了解如何配置和管理服务器,以及如何发布和调试项目。 综上所述,一个JavaWeb项目主要用到的技术包括Java编程语言、Servlet、JSP、数据库技术、前端开发技术和服务器技术等。开发者需要掌握这些技术才能够开发出稳定、高效的JavaWeb应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值