基于ldap实现登录认证

news2024/11/14 1:26:46

        最近开发的应用需要外协人员实现登录认证,外协人员的密码等信息已经录入到ldap, 需要连接ldap进行登录认证。下面先介绍一下登录的网络旅程图。

一.nginx实现AES加密

nginx请求处理入口(前端请求为json格式)

 location /aes {
        default_type text/html;
        content_by_lua_block{
            local access_filter = require 'resty.aes_auth'
            local r = access_filter.aes_auth()
            ngx.header.content_type = "application/json; charset=UTF-8"
            if r == true then 
	            ngx.say([[{"code":200,"message":"Certification         successful!","data":true,"logCode":null}]])
            else 
	            ngx.say([[{"code":401,"message":"Authentication failed!","data":false,"logCode":null }]])
            end
            ngx.exit(200)
        }

   }

 openresty请求认证接口脚本

local aes = require "resty.aes"
local cjson = require("cjson.safe")
local http = require("resty.http")
local key = "abcdefmJTNn}8Z#2`"
local iv = "1234567890123456"

local _M = {}
function _M.aes_auth()
	ngx.req.read_body()
	local args,err = ngx.req.get_body_data()

	if (not args) or (err) then
			return false
	end
	local arg_json = cjson.decode(args)
	local username = arg_json.username
	local password = arg_json.password
	if (not username) or (not password) then
			return false
	end

	local cript = aes:new(key, nil, aes.cipher(128, "cbc"), {iv=iv, method=nil})
	local pwd = cript:encrypt(password)
	if pwd then
			pwd = ngx.encode_base64(pwd)
	else
			return false
	end

	local httpc = http.new()
	local requestBody = {
			username = username,
			password = pwd
	}
	local json_body = cjson.encode(requestBody)
	local resp,err = httpc:request_uri("http://10.1.1.1:8080", {
			method = "POST",
			path = "/ldap/authUser",
			body = json_body,
			headers = {  ---header参数
					 ["Content-Type"] = "application/json;charset=UTF-8"
			}
	})
	if err then
			return false
	end
	local result = false
	if resp then
			local data = cjson.decode(resp.body).data
			if data then
					result = data
			end
	end

	return result
end

return _M

 二.应用服务调用ldap服务

引入依赖

<!--ldap-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-ldap</artifactId>
	<version>2.3.12.RELEASE</version>
</dependency>
<!--aes对称加密-->
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcprov-jdk15on</artifactId>
	<version>1.56</version>
</dependency>

AES加密解密工具类,需要注意的是nginx不支持PKCS5Padding填充方式。

package com.xxx.xxx.xxx.util;

import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import lombok.extern.slf4j.Slf4j;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.util.Base64Utils;

/**
 * description:AES对称加密工具类
 *
 * @author: lgq
 * @create: 2024-01-26 10:03
 */
@Slf4j
public class AESUtil {
    /**
     * 日志相关
     */
    /**
     * 编码
     */
    private static final String ENCODING = "UTF-8";
    /**
     * 算法定义
     */
    private static final String AES_ALGORITHM = "AES";
    /**
     * 指定填充方式
     */
    private static final String CIPHER_PADDING = "AES/ECB/PKCS5Padding";
    //必须使用PKCS7Padding,因为nginx不支持PKCS5Padding填充方式
    private static final String CIPHER_CBC_PADDING = "AES/CBC/PKCS7Padding";
    /**
     * 偏移量(CBC中使用,增强加密算法强度)
     */
    private static final String IV_SEED = "1234567890123456";
   
    private static final String RANDOM_SECRET = "abcefmJTNn}8Z#2`";

    static {
        // 指定使用bouncycastle包来加密, 引入目的就是为了支持AES/CBC/PKCS7Padding
        Security.addProvider(new BouncyCastleProvider());
    }


    public static String getRandomSecret() {
        return RANDOM_SECRET;
    }

    /**
     * AES加密
     *
     * @param content 待加密内容
     * @param aesKey  密码
     * @return
     */
    public static String encrypt(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES encrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置加密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
                //选择加密
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
                //根据待加密内容生成字节数组
                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
                //返回base64字符串
                return Base64Utils.encodeToString(encrypted);
            } catch (Exception e) {
                log.info("AES encrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES encrypt: the aesKey is null or error!");
            return null;
        }
    }

    /**
     * 解密
     *
     * @param content 待解密内容
     * @param aesKey  密码
     * @return
     */
    public static String decrypt(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES decrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置解密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
                //选择解密
                cipher.init(Cipher.DECRYPT_MODE, skeySpec);

                //先进行Base64解码
                byte[] decodeBase64 = Base64Utils.decodeFromString(content);

                //根据待解密内容进行解密
                byte[] decrypted = cipher.doFinal(decodeBase64);
                //将字节数组转成字符串
                return new String(decrypted, ENCODING);
            } catch (Exception e) {
                log.info("AES decrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES decrypt: the aesKey is null or error!");
            return null;
        }
    }

    /**
     * AES_CBC加密
     *
     * @param content 待加密内容
     * @param aesKey  密码
     * @return
     */
    public static String encryptCBC(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES_CBC encrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置加密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
                //偏移
                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
                //选择加密
                cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);//, iv
                //根据待加密内容生成字节数组
                byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
                //返回base64字符串
                return Base64Utils.encodeToString(encrypted);
            } catch (Exception e) {
                log.info("AES_CBC encrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES_CBC encrypt: the aesKey is null or error!");
            return null;
        }
    }

    /**
     * AES_CBC解密
     *
     * @param content 待解密内容
     * @param aesKey  密码
     * @return
     */
    public static String decryptCBC(String content, String aesKey) {
        if (StringUtils.isBlank(content)) {
            log.info("AES_CBC decrypt: the content is null!");
            return null;
        }
        //判断秘钥是否为16位
        if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
            try {
                //对密码进行编码
                byte[] bytes = aesKey.getBytes(ENCODING);
                //设置解密算法,生成秘钥
                SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
                //偏移
                IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));
                // "算法/模式/补码方式"
                Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
                //选择解密
                cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

                //先进行Base64解码
                byte[] decodeBase64 = Base64Utils.decodeFromString(content);

                //根据待解密内容进行解密
                byte[] decrypted = cipher.doFinal(decodeBase64);
                //将字节数组转成字符串
                return new String(decrypted, ENCODING);
            } catch (Exception e) {
                log.info("AES_CBC decrypt exception:" + e.getMessage());
                throw new RuntimeException(e);
            }

        } else {
            log.info("AES_CBC decrypt: the aesKey is null or error!");
            return null;
        }
    }

}

ladp配置类

package com.xxx.xxx.xxx.config;

import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;

/**
 * description:LdapConfig
 *
 * @author: lgq
 * @create: 2024-01-25 10:34
 */
@Configuration
public class LdapConfig {
    @Resource
    private LdapProperties ldapProperties;

    @Bean
    public LdapTemplate ldapTemplate() {
        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl(ldapProperties.getUrls());
        contextSource.setBase(ldapProperties.getBase());
        contextSource.setUserDn(ldapProperties.getUsername());
        contextSource.setPassword(ldapProperties.getPassword());
        contextSource.afterPropertiesSet();

        LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
        ldapTemplate.setIgnorePartialResultException(true);
        ldapTemplate.setDefaultTimeLimit(1000);
        ldapTemplate.setDefaultCountLimit(100);

        return ldapTemplate;
    }


}


package com.xxx.xxx.xxx.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * description:ldapProperties
 *
 * @author: lgq
 * @create: 2024-01-25 18:13
 */
@Data
@ConfigurationProperties(prefix = "spring.ldap")
public class LdapProperties {
    /**
     * ldap服务地址
     */
    private String urls;

    /**
     * 用户账号
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * base路径
     */
    private String base;
}


yml文件配置:

spring:
  profiles: prod
  application:
    name: service-xxx
  ldap:
    urls: "ldap://10.1.1.1:389"
    password: "xxxxxxxx"
    username: "cn=xxx.LDAP,ou=xxx,ou=xxx,dc=xxx,dc=xxx"
    base: "dc=xxx,dc=xxx"

认证服务类

package com.xxx.xxx.xxx.service.impl;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

import javax.annotation.Resource;

import com.xxx.xxx.xxx.service.LdapService;
import com.xxx.xxx.xxx.util.AESUtil;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Service;

/**
 * description:LdapServiceImpl
 *
 * @author: lgq
 * @create: 2024-01-26 09:26
 */
@Service
@Slf4j
public class LdapServiceImpl implements LdapService {
    @Resource
    private LdapTemplate ldapTemplate;

    /**
     * 验证登录用户的账号密码是否正确
     * @param username
     * @param password
     * @return
     */
    @Override
    public boolean authLoginUser(String username, String password) {
        if (ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(password)) {
            return false;
        }

        /**
         * aes对password进行解密
         */
        String content = AESUtil.decryptCBC(password, AESUtil.getRandomSecret());
        if (ObjectUtils.isEmpty(content)) {
            return false;
        }

        String baseDn = "";
        String filter = "sAMAccountName=" + username;
        boolean result = false;
        try {
            result = ldapTemplate.authenticate(baseDn, filter, content);
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        } catch (Error er) {
            log.error(er.getMessage(), er);
        }
        return result;
    }

}

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

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

相关文章

解锁Web3:数字未来的大门

随着科技的不断推进&#xff0c;我们正站在数字时代的新门槛上。Web3&#xff0c;作为互联网的下一个演进阶段&#xff0c;正在逐渐揭开数字未来的面纱。本文将深入探讨Web3的本质、对社会的影响以及在数字时代中所扮演的关键角色。 什么是Web3&#xff1f; Web3是互联网发展的…

数据分析讲课笔记02:科学计算库Numpy

文章目录 零、学习目标一、认识NumPy数组对象&#xff08;一&#xff09;N维数组对象ndarray&#xff08;二&#xff09;ndarray对象重要的属性&#xff08;三&#xff09;ndarray数组案例演示 二、创建NumPy数组&#xff08;一&#xff09;采用array()函数创建数组&#xff08…

父元素flex:1 高度却被子元素撑开的问题

问题 当父元素设置了flex: 1; 的情况下&#xff0c;想在其中子元素超出父元素高度的情况下&#xff0c;产生滚动条&#xff0c;在父元素区域滚动。由于子元素高度不固定&#xff0c;故父元素设置为display: flex; flex-direction: column; 子元素设置flex: 1; overflow: auto;…

select的change方法如何传递多个参数

element-ui中select的change方法传递多个参数 element-ui中的select&#xff0c;checkbox等组件的change方法的回调函数只有当前选择的val&#xff0c;如果想再传入自定义参数怎么办&#xff1f; 不能够传入自定义的参数&#xff0c;在进行某些操作时&#xff0c;会比较困难&…

【Java程序设计】【C00179】基于SSM的电影在线购票管理系统(论文+PPT)

基于SSM的电影在线购票管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的电影在线购票管理系统 本系统分为前台用户和后台管理员2个功能模块。 前台用户&#xff1a;当游客打开系统的网址后&#xff0c;首先看到…

【开源】基于JAVA+Vue+SpringBoot的用户画像活动推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 兴趣标签模块2.3 活动档案模块2.4 活动报名模块2.5 活动留言模块 三、系统设计3.1 用例设计3.2 业务流程设计3.3 数据流程设计3.4 E-R图设计 四、系统展示五、核心代码5.1 查询兴趣标签5.2 查询活动推荐…

一张图搞清楚HTTP状态码

HTTP状态码的基本概念 在客户端和服务器连接交互的时候&#xff0c;一般是客户端先给服务器发送请求&#xff0c;然后服务器返回结果。客户端和服务器之间的交互非常频繁&#xff0c;涉及到很多种不同类型的操作&#xff0c;大多数的时候服务器能成功返回结果&#xff0c;有时…

Vue3使用setup-extend简化组件名写法

如果我们在Vue3中要使用setup的语法糖&#xff0c;就需要使用两个script标签&#xff0c;一个用于设置组件的name属性&#xff0c;一个用于编写setup中的代码。如下&#xff1a; 但是我们有觉得光是因为一个name属性就多写一个script标签有点麻烦了。 因此我们可以使用插件来进…

[每日一题] 01.30

文章目录 数列求和质数口袋奇怪数求和 数列求和 n int(input()) print(sum([i for i in range(1,n 1)]))质数口袋 n int(input()) i 2 sum 0 count 0 while n - i > sum:flag Truefor j in range(2,i): # 判断i是否为素数if i % j 0:flag Falsebreakif flag:sum i…

如何解决 DNS 解析错误(DNS_PROBE_FINISHED_NXDOMAIN)问题

如何解决 DNS 解析错误&#xff08;DNS_PROBE_FINISHED_NXDOMAIN&#xff09;问题 导语&#xff1a; 当你在访问网站时遇到 DNS 解析错误&#xff08;DNS_PROBE_FINISHED_NXDOMAIN&#xff09;时&#xff0c;可能是由于本地 DNS 缓存导致的问题。这里介绍一种简单且常见的解决…

MongoDB安装以及卸载

查询id&#xff1a; docker ps [rootlocalhost ~]# docker stop c7a8c4ac9346 c7a8c4ac9346 [rootlocalhost ~]# docker rm c7a8c4ac9346 c7a8c4ac9346 [rootlocalhost ~]# docker rmi mongo sudo docker pull mongo:4.4 sudo docker images 卸载旧的 sudo docker stop mong…

【Linux】vim的简单使用

我们知道在Windows下的VS2019是一个集成开发环境&#xff0c;也就是说&#xff0c;集编辑&#xff0c;编译&#xff0c;调试等功能都放在了一起&#xff1b;但是在Linux下&#xff0c;这些步骤都是分开的&#xff0c;我们这篇博客就来说一说vim这个编辑器&#xff0c;它只有编辑…

Focaler-IoU:更聚焦的IoU损失

摘要 边界框回归在目标检测领域中起着至关重要的作用&#xff0c;而目标检测的定位精度在很大程度上取决于边界框回归的损失函数。现有的研究通过利用边界框之间的几何关系来提高回归性能&#xff0c;而忽略了难易样本分布对边界框回归的影响。本文分析了难易样本分布对回归结…

Kotlin基础——高阶函数和内联函数

高阶函数 高阶函数以另一个函数作为参数或者返回值&#xff0c;其可用Lambda或函数引用表示 函数类型 下面将Lambda存储在sum变量中&#xff0c;其是函数类型 val sum { x: Int, y: Int -> x y }完整的函数类型为(para1,prar2…) -> returnValue val a: Int 0 va…

【MBtiles数据索引和服务发布】GeoServer改造Springboot番外系列二

xyz地图服务访问示例&#xff1a;http://192.168.1.240:8081/gmserver/raster/xyz/firstWP:Imagery-raster/{z}/{x}/{y}.jpg 访问示例如下&#xff1a; mbtiles目录结构 根据z&#xff0c;x&#xff0c;y获取对应mbtiles文件路径的工具方法 说明&#xff1a;重点是使用getMb…

堆宝塔

L2-1 堆宝塔 分数 25 作者 陈越 单位 浙江大学 堆宝塔游戏是让小朋友根据抓到的彩虹圈的直径大小&#xff0c;按照从大到小的顺序堆起宝塔。但彩虹圈不一定是按照直径的大小顺序抓到的。聪明…

第六讲_JavaScript原型

JavaScript原型 1. 原型的概念2. 原型继承2.1 原型链 3. class类的原型对象 1. 原型的概念 原型是 JavaScript 对象相互继承特性的机制。 每个函数都有一个 prototype 属性&#xff0c;这个属性指向一个对象&#xff0c;这个对象称为原型对象。每个对象都有一个 [[Prototype]…

在线mockjson

在线mockjson体验地址 在调一个问题的时候&#xff0c;但是问题的数据可能并不能随着想到的场景就变化&#xff0c;譬如说又个数组长度的情况&#xff0c;可能默认的情况下是返回4个元素&#xff0c;但是想要返回为空的时候&#xff0c;如果联系服务给改一下&#xff0c;那么流…

Pyecharts炫酷热力图:参数详解与实战大揭秘

Pyecharts绘制多种炫酷热力图参数说明代码实战 引言 热力图在数据可视化中是一种强大的工具&#xff0c;可以直观地展示数据的分布情况和变化趋势。Pyecharts是一个基于Echarts的Python可视化库&#xff0c;提供了丰富的图表类型&#xff0c;包括热力图。在本文中&#xff0c…

防御保护---防火墙的NAT-easyip

文章目录 目录 文章目录 点击此处查看NAT技术详解 一.NAT分类 二.防火墙配置 一对多NAT配置 多对多NAT配置 练习 点击此处查看NAT技术详解 一.NAT分类 源IP-NAT(源IP与公网之间转换)&#xff1a;静态NAT&#xff0c;动态NAT&#xff0c;NAPT&#xff1b;使内网能够访问公网目…