All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.rexsheng.springboot.faster.system.auth.application.AuthServiceImpl Maven / Gradle / Ivy

The newest version!
package com.github.rexsheng.springboot.faster.system.auth.application;

import com.github.rexsheng.springboot.faster.common.constant.CommonConstant;
import com.github.rexsheng.springboot.faster.common.constant.SecurityConstant;
import com.github.rexsheng.springboot.faster.common.utils.JwtUtil;
import com.github.rexsheng.springboot.faster.kaptcha.DefaultTextKaptchaProducer;
import com.github.rexsheng.springboot.faster.kaptcha.Kaptcha;
import com.github.rexsheng.springboot.faster.kaptcha.KaptchaProducer;
import com.github.rexsheng.springboot.faster.kaptcha.configuration.KaptchaProperties;
import com.github.rexsheng.springboot.faster.mail.application.JavaMailService;
import com.github.rexsheng.springboot.faster.system.auth.application.dto.*;
import com.github.rexsheng.springboot.faster.system.auth.application.security.InvalidTokenException;
import com.github.rexsheng.springboot.faster.system.auth.application.security.TokenExpireProperties;
import com.github.rexsheng.springboot.faster.system.auth.domain.*;
import com.github.rexsheng.springboot.faster.system.auth.domain.gateway.AuthCodeDO;
import com.github.rexsheng.springboot.faster.system.auth.domain.gateway.AuthGateway;
import com.github.rexsheng.springboot.faster.system.menu.application.dto.MenuDetailResponse;
import com.github.rexsheng.springboot.faster.system.menu.domain.SysMenuType;

import com.github.rexsheng.springboot.faster.util.DateUtil;
import com.github.rexsheng.springboot.faster.util.PasswordUtils;
import com.nimbusds.jose.JOSEException;
import jakarta.mail.MessagingException;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.www.NonceExpiredException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.imageio.ImageIO;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@ConditionalOnClass({Authentication.class,SqlSessionFactoryBean.class})
public class AuthServiceImpl implements AuthService{

    private static final Logger logger= LoggerFactory.getLogger(AuthServiceImpl.class);

    private UserDomainService userDomainService;

    private MenuDomainService menuDomainService;

    private DeptDomainService deptDomainService;

    private AuthGateway authGateway;

    private TokenContainer tokenContainer;

    private TokenExpireProperties tokenExpireProperties;

    private KaptchaProperties kaptchaProperties;

    private KaptchaProducer kaptchaProducer;

    @Autowired(required = false)
    private JavaMailService mailService;

    public AuthServiceImpl(UserDomainService userDomainService, MenuDomainService menuDomainService, DeptDomainService deptDomainService,
                           AuthGateway authGateway, TokenContainer tokenContainer, TokenExpireProperties tokenExpireProperties,
                           KaptchaProperties kaptchaProperties, KaptchaProducer kaptchaProducer) {
        this.userDomainService = userDomainService;
        this.menuDomainService = menuDomainService;
        this.deptDomainService = deptDomainService;
        this.authGateway = authGateway;
        this.tokenContainer = tokenContainer;
        this.tokenExpireProperties=tokenExpireProperties;
        this.kaptchaProperties=kaptchaProperties;
        this.kaptchaProducer = kaptchaProducer;
    }

    @Override
    public SysUserDetail login(LoginRequest request) {
        SysUserDetail user=request.toUser();
        SysUserDetail queryUserResult=userDomainService.queryByAccount(user);
        if(queryUserResult==null || !queryUserResult.validatePassword(user.getPassword())){
            return null;
        }
        loadUserAuthorities(queryUserResult);

        LocalDateTime loginTime=DateUtil.currentDateTime();
        userDomainService.updateLoginTime(queryUserResult.getUserId(),loginTime);
        queryUserResult.setLoginTime(DateUtil.toDate(loginTime));
        return queryUserResult;
    }

    public SysUserDetail loadUser(LoadUserRequest request) {
        SysUserDetail user=userDomainService.queryById(request.getUserId());
        loadUserAuthorities(user);
        return user;
    }

    public void loadUserAuthorities(SysUserDetail user){
        List allMenus=flatMap(menuDomainService.queryList());
        List authCodeDOList=authGateway.queryAuthByUserId(user.getUserId());
        Set authCodeSet=authCodeDOList.stream().filter(a->!ObjectUtils.isEmpty(a.getRoleCode())).map(a->"ROLE_"+a.getRoleCode()).collect(Collectors.toSet());
        List filteredMenuList=authCodeDOList.stream().flatMap(auth -> flatMap(allMenus,auth.getMenuId()).stream())
                .peek(a->{
                    if(!ObjectUtils.isEmpty(a.getMenuCode())){
                        authCodeSet.add(a.getMenuCode());
                    }
                })
                .filter(a-> SysMenuType.MENU.getCode().equals(a.getMenuType()))
                .collect(Collectors.toMap(MenuDetailResponse::getMenuId, Function.identity(),(p1, p2)->p1)).values()
                .stream()
                .sorted(Comparator.comparing(MenuDetailResponse::getMenuOrder,Comparator.comparing(a->a==null?0:a)).thenComparing(MenuDetailResponse::getMenuId))
                .collect(Collectors.toList());

        user.setAuthorities(new ArrayList<>(authCodeSet).stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
        user.setRoles(authCodeDOList.stream().filter(a->!ObjectUtils.isEmpty(a.getRoleCode())).map(a->a.getRoleCode()).distinct().collect(Collectors.toList()));
        user.setMenus(buildMenuTree(filteredMenuList,null));
        user.setDeptIds(deptDomainService.queryDeptAndChildrenIds(user.getDeptId()));
    }

    private List flatMap(List list){
        List result=new ArrayList<>();
        for(MenuDetailResponse item:list){
            result.add(item);
            if(!ObjectUtils.isEmpty(item.getChildren())){
                result.addAll(flatMap(item.getChildren()));
            }
        }
        return result;
    }

    private List flatMap(List allMenus,Integer leafId){
        List dataList=new ArrayList<>();
        Optional leafOpt=allMenus.stream().filter(a->a.getMenuId().equals(leafId)).findFirst();
        if(leafOpt.isPresent()){
            MenuDetailResponse leaf=leafOpt.get();
            dataList.add(leaf);
            if(leaf.getParentId()!=null){
                dataList.addAll(flatMap(allMenus,leaf.getParentId()));
            }
        }

        return dataList;
    }

    private List buildMenuTree(List list, Integer parentId){
        return list.stream().filter(a->parentId==null?a.getParentId()==null:parentId.equals(a.getParentId()))
                .map(a->{
                    SysUserDetail.SysUserMenu menu=new SysUserDetail.SysUserMenu();
                    menu.setMenuId(a.getMenuId());
                    menu.setMenuName(a.getMenuName());
                    menu.setMenuOrder(a.getMenuOrder());
                    menu.setMenuPath(a.getMenuPath());
                    menu.setMenuPathQuery(a.getMenuPathQuery());
                    menu.setParentId(a.getParentId());
                    menu.setMenuIcon(a.getMenuIcon());
                    menu.setComponent(a.getComponent());
                    menu.setCache(a.getCache());
                    menu.setVisible(a.getVisible());
                    menu.setFrame(a.getFrame());
                    menu.setFullscreen(a.getFullscreen());
                    menu.setTag(a.getTag());
                    menu.setChildren(buildMenuTree(list,a.getMenuId()));
                    return menu;
                }).collect(Collectors.toList());
    }

    @Override
    public Map createSessionToken(SysUserDetail user) {
        return refreshSessionToken(user,null);
    }

    @Override
    public Map refreshSessionToken(RefreshTokenRequest request) {
        if(request==null){
            return null;
        }
        final String refreshToken=request.getRefreshToken();
        if(ObjectUtils.isEmpty(refreshToken)){
            return null;
        }
        SysUserDetail user=tokenContainer.get(SecurityConstant.REFRESH_TOKEN_REDIS_KEY_PREFIX+refreshToken);
        if(user==null){
            return null;
        }
        return refreshSessionToken(user,refreshToken);
    }

    private Map refreshSessionToken(SysUserDetail user,String refreshToken) {
        Map result=new LinkedHashMap<>();
        Map params=new LinkedHashMap<>();
        params.put("u",user.getUserId());
        final boolean isRefreshReq=refreshToken!=null;
        if(isRefreshReq){
            params.put("r",refreshToken);
        }
        else{
            if(user.isRefreshTokenSupport()){
                JwtUtil.JwtPayload jwtPayload=new JwtUtil.JwtPayload();
                jwtPayload.setSubject(user.getUserId().toString());
                try {
                    refreshToken = JwtUtil.createToken(jwtPayload, SecurityConstant.TOKEN_JWT_SECRET_KEY);
                } catch (JOSEException e) {
                    throw new RuntimeException("Create Refresh Token Error");
                }
                tokenContainer.save(SecurityConstant.REFRESH_TOKEN_REDIS_KEY_PREFIX+refreshToken,user,tokenExpireProperties.getRefreshExpires().getSeconds());
                params.put("r",refreshToken);
            }
        }
        String token= null;
        try {
            JwtUtil.JwtPayload jwtPayload=new JwtUtil.JwtPayload();
            jwtPayload.setExpireDate(DateUtil.toDate(DateUtil.currentDateTime().plusSeconds(tokenExpireProperties.getExpires().getSeconds())));
            jwtPayload.setPayload(params);
            jwtPayload.setSubject(user.getAccount());
            token = JwtUtil.createToken(jwtPayload, SecurityConstant.TOKEN_JWT_SECRET_KEY);
        } catch (Exception e) {
            throw new RuntimeException("Create Token Error");
        }
        tokenContainer.save(SecurityConstant.TOKEN_REDIS_KEY_PREFIX+user.getAccount()+":"+token,user,tokenExpireProperties.getExpires().getSeconds());
        result.put("access_token",token);
        result.put("expires_in",tokenExpireProperties.getExpires().getSeconds());
        if(isRefreshReq){
            if(tokenExpireProperties.getRefreshRenew()){
                tokenContainer.renew(SecurityConstant.REFRESH_TOKEN_REDIS_KEY_PREFIX+refreshToken,tokenExpireProperties.getRefreshExpires().getSeconds());
                result.put("refresh_token",refreshToken);
                result.put("refresh_expires_in",tokenExpireProperties.getRefreshExpires().getSeconds());
            }
        }
        else{
            result.put("refresh_token",refreshToken);
            result.put("refresh_expires_in",tokenExpireProperties.getRefreshExpires().getSeconds());
        }
        result.put("token_type","Bearer");
        return result;
    }

    @Override
    public SysUserDetail getUserBySessionToken(String token) {
        try {
            JwtUtil.JwtPayload jwtPayload = JwtUtil.verifyToken(token, SecurityConstant.TOKEN_JWT_SECRET_KEY);
            if(!jwtPayload.isValid()){
                throw new InvalidTokenException("expire date: "+jwtPayload.getExpireDate());
            }
            String loginAccount = jwtPayload.getSubject();
            String key=SecurityConstant.TOKEN_REDIS_KEY_PREFIX+loginAccount+":"+token;
            return tokenContainer.get(key);
        } catch (InvalidTokenException e) {
            logger.warn("jwt已过期: {}, detail: {}", token, e.getMessage());
            throw new NonceExpiredException("登录已失效,请重新登录");
        } catch (Exception e) {
            logger.warn("jwt解析异常",e);
            throw new NonceExpiredException("登录已失效,请重新登录");
        }
    }

    @Override
    public SysUserDetail logout(String token) {
        try {
            JwtUtil.JwtPayload jwtPayload=JwtUtil.verifyToken(token,SecurityConstant.TOKEN_JWT_SECRET_KEY);
            String loginAccount=jwtPayload.getSubject();
            String refreshToken= (String) jwtPayload.getPayload().get("r");
            tokenContainer.remove(SecurityConstant.TOKEN_REDIS_KEY_PREFIX+loginAccount+":"+token);
            if(refreshToken!=null){
                tokenContainer.remove(SecurityConstant.REFRESH_TOKEN_REDIS_KEY_PREFIX+refreshToken);
            }

            SysUserDetail output=new SysUserDetail();
            output.setUserId((Long) jwtPayload.getPayload().get("u"));
            return output;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Map createKaptcha() throws IOException {
        if(!Boolean.TRUE.equals(kaptchaProperties.getEnabled())){
            return new HashMap<>();
        }
        Kaptcha kaptcha=kaptchaProducer.createKaptcha();
        String uuid= UUID.randomUUID().toString();
        Map result=new HashMap<>();
        result.put("uuid",uuid);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            ImageIO.write(kaptcha.getImage(), "jpg", outputStream);
            // 对字节数组Base64编码
            Base64.Encoder encoder = Base64.getEncoder();
            result.put("img",encoder.encodeToString(outputStream.toByteArray()));
        }
        finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                }
            }
        }
        tokenContainer.save("kaptcha:"+uuid,kaptcha.getText(),60*3L);
        return result;
    }

    @Override
    public boolean validateKaptcha(String uuid, String code) {
        if(!Boolean.TRUE.equals(kaptchaProperties.getEnabled())){
            return true;
        }
        String value=tokenContainer.getAndDel("kaptcha:"+uuid);
        if(value!=null){
            return value.equalsIgnoreCase(code);
        }
        return false;
    }

    @Override
    public SysUserDetail getUserByApiToken(String tokenName, String tokenValue) {
        SysUserDetail queryUserResult=userDomainService.queryByApiToken(tokenName,tokenValue);
        if(queryUserResult==null){
            return null;
        }
        return queryUserResult;
    }

    @Override
    public void offline(OfflineUserRequest request) {
        List userList=userDomainService.queryList(request.getUserIds());
        if(!ObjectUtils.isEmpty(userList)){
            for (SysUserDetail user : userList) {
                String prefix=SecurityConstant.TOKEN_REDIS_KEY_PREFIX+user.getAccount()+":";
                List keys=tokenContainer.getKeysWithPrefix(prefix);
                if(!ObjectUtils.isEmpty(keys)){
                    for (String key : keys) {
                        tokenContainer.remove(key);
                        String token=key.substring(prefix.length());
                        try {
                            JwtUtil.JwtPayload jwtPayload = JwtUtil.verifyToken(token, SecurityConstant.TOKEN_JWT_SECRET_KEY);
                            String refreshToken= (String) jwtPayload.getPayload().get("r");
                            if(refreshToken!=null){
                                tokenContainer.remove(SecurityConstant.REFRESH_TOKEN_REDIS_KEY_PREFIX+refreshToken);
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
                prefix=SecurityConstant.REFRESH_TOKEN_REDIS_KEY_PREFIX;
                List refreshTokenKeys=tokenContainer.getKeysWithPrefix(prefix);
                if(!ObjectUtils.isEmpty(refreshTokenKeys)){
                    for (String key : refreshTokenKeys) {
                        String token=key.substring(prefix.length());
                        try {
                            JwtUtil.JwtPayload jwtPayload = JwtUtil.verifyToken(token, SecurityConstant.TOKEN_JWT_SECRET_KEY);
                            String userId=jwtPayload.getSubject();
                            if(userId!=null && userId.equals(String.valueOf(user.getUserId()))){
                                tokenContainer.remove(key);
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    }

    @Override
    public FindUserByAccountResponse validateUserAccount(ValidateAccountRequest request) {
        if(validateKaptcha(request.getUuid(),request.getCaptcha())){
            return findUserByAccount(request.getAccount());
        }
        throw new RuntimeException("验证码错误!");
    }

    public FindUserByAccountResponse findUserByAccount(String account){
        SysUserDetail userDetail=new SysUserDetail();
        userDetail.setAccount(account);
        SysUserDetail user= userDomainService.queryByAccount(userDetail);
        if(user!=null && user.isEnabled()){
            FindUserByAccountResponse response=new FindUserByAccountResponse();
            String uid= PasswordUtils.encodeRSAPrivate(String.valueOf(user.getUserId()), CommonConstant.RSA_PRIVATE_KEY_INNER);
            response.setUid(uid);
            response.setMail(user.getMail());
            response.setPhone(user.getPhone());
            return response;
        }
        throw new RuntimeException("用户不存在!");
    }

    @Override
    public void sendResetPasswordMail(String uid) {
        if(mailService==null){
            throw new RuntimeException("邮件服务异常,请联系管理员!");
        }
        String userId=PasswordUtils.decodeRSAPublic(uid,CommonConstant.RSA_PUBLIC_KEY_INNER);
        if(!ObjectUtils.isEmpty(userId)){
            SysUserDetail user=userDomainService.queryById(Long.valueOf(userId));
            if(user!=null && user.isEnabled() && !ObjectUtils.isEmpty(user.getMail())){
                KaptchaProperties properties=new KaptchaProperties();
                properties.setCharLength(6);
                properties.setCharScope("1234567890");
                String code=new DefaultTextKaptchaProducer(properties).createRandomText();
                String key="ResetPasswordKaptcha:"+userId+":mail";
                tokenContainer.remove(key);
                tokenContainer.save(key,code,60*2L);
                mailService.send(mimeMessageHelper -> {
                    try {
                        mimeMessageHelper.setSubject("密码重置通知");
                        mimeMessageHelper.setTo(user.getMail());
                        mimeMessageHelper.setText("您正在重置账号\""+user.getAccount()+"\"的密码,验证码为"+code+"(2分钟内有效)。",true);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
                logger.info("找回密码已发送邮件, userId: {}, mail: {}, kaptcha: {}", userId, user.getMail(), code);
                return;
            }
        }
        throw new RuntimeException("用户或邮箱不存在!");
    }

    @Override
    public FindUserByAccountResponse validateUserRemoteKaptcha(ValidateUserRemoteKaptchaRequest request) {
        String userId=PasswordUtils.decodeRSAPublic(request.getUid(),CommonConstant.RSA_PUBLIC_KEY_INNER);
        if(!ObjectUtils.isEmpty(userId)){
            SysUserDetail user=userDomainService.queryById(Long.valueOf(userId));
            if(user!=null && user.isEnabled()){
                String key="ResetPasswordKaptcha:"+userId+":"+request.getSender();
                String kaptcha=tokenContainer.get(key);
                if(ObjectUtils.nullSafeEquals(kaptcha,request.getCaptcha())){
                    tokenContainer.remove(key);
                    FindUserByAccountResponse response=new FindUserByAccountResponse();
                    response.setAccount(PasswordUtils.encodeRSAPrivate(user.getAccount(), CommonConstant.RSA_PRIVATE_KEY_INNER));
                    return response;
                }
                throw new RuntimeException("验证码错误!");
            }
        }
        throw new RuntimeException("用户不存在!");
    }

    @Override
    public void findPassword(FindPasswordRequest request) {
        String userId=PasswordUtils.decodeRSAPublic(request.getUid(),CommonConstant.RSA_PUBLIC_KEY_INNER);
        if(!ObjectUtils.isEmpty(userId)){
            SysUserDetail user=userDomainService.queryById(Long.valueOf(userId));
            if(user!=null && user.isEnabled()
                    && !ObjectUtils.isEmpty(user.getAccount())
                    && user.getAccount().equals(PasswordUtils.decodeRSAPublic(request.getAccount(),CommonConstant.RSA_PUBLIC_KEY_INNER))){
                userDomainService.changePassword(user.getUserId(),request.getPassword());
                return;
            }
        }
        throw new RuntimeException("用户不存在!");
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy