主要逻辑如下:
// 获取传递的用户名称、itcode
String username = loginParam.getUsername();
String itCode;
if (username.endsWith(LdapProperties.REALM)) {
//裁掉“@leinovo.com”
itCode = username.replace(LdapProperties.REALM, "");
} else {
itCode = username;
//邮箱地址补全
username = username.concat(LdapProperties.REALM);
}
// 获取加密后的登录密码
String password = loginParam.getPassword();
// 解析登录密码;
try {
password = AesUtils.aesDecrypt(password, jwtProperties.getSecret());
} catch (Exception e) {
log.error("密码解密异常...");
return ApiResult.fail(ApiCode.LOGIN_EXCEPTION);
}
根据用户名和密码,从ldap 中获取用户
User user;
try {
DirContext dirContext = ldapMapper.connectLDAP(username, password);
user = ldapMapper.getAccountInfo(dirContext, username);
} catch (NamingException e) {
log.error("登陆失败,userName:{}", loginParam.getUsername());
return ApiResult.fail(ApiCode.LOGIN_EXCEPTION);
}
if (user == null) {
log.error("登陆失败,用户信息异常,userName:{}", loginParam.getUsername());
return ApiResult.fail(ApiCode.LOGIN_EXCEPTION);
}
String itCode = user.getItcode();
AcUser acUser = userMapper.getByItcode(itCode);
LoginUserVo loginUserVo;
Set<String> roleNames = new HashSet<>();
//dbUser为空说明该用户为首次登录,需要赋予默认的角色
if (acUser == null) {
//将用户信息存入数据库
userMapper.insert(SysUserConvert.INSTANCE.userToAcUser(user));
// 查询默认角色
//授予默认的角色权限
LambdaQueryWrapper<Role> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(Role::getName, CommonConstant.DEFAULT_ROLE_NAME);
Role role = roleMapper.selectOne(queryWrapper);
if (role == null) {
log.error("The default role is missing, please contact the administrator!");
return ApiResult.fail("The default role is missing, please contact the administrator!");
}
// 保存关联关系
AcRoleUser userRole = new AcRoleUser();
userRole.setRoleid(role.getId());
userRole.setUserid(user.getId());
roleUserMapper.insert(userRole);
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
// 获取加密的盐
//使用默认盐值:LEINOVO@QIT^ABC$$
String salt = jwtProperties.getSecret();
// 生成token字符串并返回,失效时间1小时;
Duration expireDuration = Duration.ofSeconds(jwtProperties.getExpireSecond());
/**
* 生成token:每个人的token是固定的,只要认证了,下次可以继续使用
* jwtToken:也是固定的,登录后,下次继续用;
* shiro认证登录:subject.login(jwtToken)
*/
String token = JwtUtil.generateToken(loginUserVo.getItCode(), salt, expireDuration);
log.debug("token:{}", token);
// 使用 itcode okensaltexpireTime 完成shiro 认证注册;
// 创建AuthenticationToken
JwtToken jwtToken = JwtToken.build(token, loginUserVo.getItCode(), salt, jwtProperties.getExpireSecond());
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 执行认证登陆
subject.login(jwtToken);
保存token 后,返回登录成功
// 缓存 tokenjwtTokensaltloginUserVoexpireTime 到Redis
loginRedisService.cacheLoginInfo(jwtToken, loginUserVo, true);
// 设置响应头
response.setHeader(JwtTokenUtil.getTokenName(), token);
log.debug("登陆成功,username:{}", loginUserVo.getItCode());
// 设置返回三大参数(token,loginUserVo, homePage) todo homePage 需要进一步确认
// 返回token
Map map = new HashMap();
map.put("token", token);
map.put("userMsg", loginUserVo);
return ApiResult.ok(map, "登陆成功");
登录后,shiro框架会拦截请求,校验接口 request 中token的正确性,做出权限控制。
package com.leinovo.npi.auth.shiro.ldap;
import com.leinovo.platform.common.entity.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import java.util.Hashtable;
/**
* @author: Liming
* @date: 2019/10/17
*/
@Repository
public class LdapMapper {
@Autowired
private LdapProperties ldapProperties;
/**
* 获取ldap账户信息
*
* @param ctx
* @param userName
* @return
*/
public User getAccountInfo(DirContext ctx, String userName) throws NamingException {
String name = userName;
if (userName.endsWith(LdapProperties.REALM)) {
//裁掉“@leinovo.com”
name = userName.replace(LdapProperties.REALM, "");
}
String filter = StringUtils.replace(ldapProperties.getSearchFilter(), "<uid>", name);
String[] attrPersonArray = {LdapProperties.EMAIL, LdapProperties.PHONE, LdapProperties.ADDRESS,
LdapProperties.DISPLAYNAME, LdapProperties.DEPARTMENT, LdapProperties.MANAGER};
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(attrPersonArray);
NamingEnumeration<SearchResult> answer = ctx.search(ldapProperties.getContext(), filter, searchControls);
if (answer.hasMore()) {
SearchResult result = answer.next();
NamingEnumeration<? extends Attribute> attrs = result.getAttributes().getAll();
User user = new User();
while (attrs.hasMore()) {
Attribute attr = attrs.next();
if (LdapProperties.EMAIL.equals(attr.getID())) {
user.setEmail(StringUtils.substring(String.valueOf(attr.get()), 0, 32));
} else if (LdapProperties.PHONE.equals(attr.getID())) {
user.setPhone(StringUtils.substring(String.valueOf(attr.get()), 0, 20));
} else if (LdapProperties.ADDRESS.equals(attr.getID())) {
user.setAddress(StringUtils.substring(String.valueOf(attr.get()), 0, 100));
} else if (LdapProperties.DISPLAYNAME.equals(attr.getID())) {
user.setRealname(StringUtils.substring(String.valueOf(attr.get()), 0, 99));
} else if (LdapProperties.DEPARTMENT.equals(attr.getID())) {
user.setDepartment(StringUtils.substring(String.valueOf(attr.get()), 0, 100));
} else if (LdapProperties.MANAGER.equals(attr.getID())) {
String managerAttrValue = String.valueOf(attr.get());
String manager = (managerAttrValue.split(",")[0].split("="))[1];
user.setManager(manager);
}
}
return user;
}
return null;
}
/**
* 使用java连接AD域
*
* @param username 用户名
* @param password 密码
* @return void
* @author liming
*/
public DirContext connectLDAP(String username, String password) throws NamingException {
Hashtable<String, String> HashEnv = new Hashtable<String, String>();
// LDAP访问安全级别(none,simple,strong)
HashEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
//AD的用户名,必须确保是邮箱地址
HashEnv.put(Context.SECURITY_PRINCIPAL, username);
//AD的密码
HashEnv.put(Context.SECURITY_CREDENTIALS, password);
// LDAP工厂类
HashEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
//连接超时设置为3秒
//HashEnv.put("com.sun.jndi.ldap.connect.timeout", "3000");
// 默认端口389
HashEnv.put(Context.PROVIDER_URL, ldapProperties.getUrl());
if (ldapProperties.getUrl().contains(":636") || ldapProperties.getUrl().startsWith("ldaps://")) {
HashEnv.put(Context.SECURITY_PROTOCOL, "ssl");
HashEnv.put("java.naming.ldap.factory.socket", "com.leinovo.qit.shiro.ldap.TrustedSocketFactory");
}
// 初始化上下文
return new InitialDirContext(HashEnv);
}
}
package com.leinovo.npi.auth.shiro.ldap;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author: Liming
* @date: 2019/10/17
*/
@Data
@Component
@ConfigurationProperties(prefix = "pss-portal.ldap")
public class LdapProperties {
public static final String EMAIL = "mail";
public static final String PHONE = "telephoneNumber";
public static final String ADDRESS = "physicalDeliveryOfficeName";
public static final String MANAGER = "manager";
public static final String REALNAME = "msExchExtensionAttribute16";
public static final String DISPLAYNAME = "displayName";
public static final String DEPARTMENT = "department";
public static final String REALM = "@leinovo.com";
private String searchFilter;
private String context;
private String url;
}
package com.leinovo.npi.auth.shiro.jwt;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.leinovo.platform.util.JwtUtil;
import com.leinovo.npi.auth.shiro.util.IpUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.shiro.authc.HostAuthenticationToken;
import java.util.Date;
/**
* Shiro JwtToken对象
*
* @author geekidea
* @date 2019-09-27
* @since 1.3.0.RELEASE
**/
@Data
@Accessors(chain = true)
public class JwtToken implements HostAuthenticationToken {
/**
* 登陆ip
*/
private String host;
/**
* 登陆用户名称
*/
private String username;
/**
* 登陆盐值
*/
private String salt;
/**
* 登陆token
*/
private String token;
/**
* 创建时间
*/
private Date createDate;
/**
* 多长时间过期,默认一小时
*/
private long expireSecond;
/**
* 过期日期
*/
private Date expireDate;
private String principal;
private String credentials;
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
public static JwtToken build(String token, String username, String salt, long expireSecond) {
DecodedJWT decodedJWT = JwtUtil.getJwtInfo(token);
Date createDate = decodedJWT.getIssuedAt();
Date expireDate = decodedJWT.getExpiresAt();
return new JwtToken()
.setUsername(username)
.setToken(token)
.setHost(IpUtil.getRequestIp())
.setSalt(salt)
.setCreateDate(createDate)
.setExpireSecond(expireSecond)
.setExpireDate(expireDate);
}
}
/**
* 生成JWT Token
*
* @param username 用户名
* @param salt 盐值
* @param expireDuration 过期时间和单位
* @return token
*/
public static String generateToken(String username, String salt, Duration expireDuration) {
try {
if (StringUtils.isBlank(username)) {
log.error("username不能为空");
return null;
}
log.debug("username:{}", username);
// 如果盐值为空,则使用默认值:LEiNOVO@QIT^ABC$$
if (StringUtils.isBlank(salt)) {
salt = jwtProperties.getSecret();
}
log.debug("salt:{}", salt);
// 过期时间,单位:秒
Long expireSecond;
// 默认过期时间为1小时
if (expireDuration == null) {
expireSecond = jwtProperties.getExpireSecond();
} else {
expireSecond = expireDuration.getSeconds();
}
log.debug("expireSecond:{}", expireSecond);
Date expireDate = DateUtils.addSeconds(new Date(), expireSecond.intValue());
log.debug("expireDate:{}", expireDate);
// 生成token
Algorithm algorithm = Algorithm.HMAC256(salt);
String token = JWT.create()
.withClaim(CommonConstant.JWT_USERNAME, username)
.withJWTId(UUIDUtil.getUUID()) // jwt唯一id
.withIssuer(jwtProperties.getIssuer()) // 签发人
.withSubject(jwtProperties.getSubject()) // 主题
.withAudience(jwtProperties.getAudience()) // 签发的目标
.withIssuedAt(new Date()) // 签名时间
.withExpiresAt(expireDate) // token过期时间
.sign(algorithm); // 签名
return token;
} catch (Exception e) {
log.error("generateToken exception", e);
}
return null;
}