一、网关安全

1、OAuth2的角色和流程

2、微服务环境认证和SSO

1的方式在微服务架构下存在以下问题:

微服务网关: 

3、OAuth2授权的四种方式

二、Springboot整合JWT+Shiro(微服务架构可以整合到网关服务)

UserInfo实体类:

import lombok.Data;

import java.util.List;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@Data
public class UserInfo {
    private Integer id;

    private String username;

    private String password;

    private List<String> roles;

    private List<String> permissions;
}

Role实体类:

import lombok.Data;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@Data
public class Role {
    private Integer id;

    private String name;
}

3、Permisson实体类

import lombok.Data;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@Data
public class Permission {
    private Integer id;

    private String name;
}

1、自定义JWTToken

import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
public class JWTToken implements AuthenticationToken {
    private String token;

    public JWTToken(String token) {
        this.token = token;
    }

    /**
     * 身份
     */
    @Override
    public Object getPrincipal() {
        return token;
    }

    /**
     * 凭证
     */
    @Override
    public Object getCredentials() {
        return token;
    }
}

2、JWT工具类

import cn.hutool.crypto.digest.DigestUtil;
import com.auth0.jwt.exceptions.JWTDecodeException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@Slf4j
public class JWTUtil {
    /**
     * 设置1小时失效
     */
    private static final long EXPIRE_TIME = 60 * 1000;
    /**
     * 盐值
     */
    private static final String SALT = "4=jhKxxJM4SR&K-m";
    
    public static boolean verify(String token, int userId, String password) {
        try {
            Claims kkLoginToken = Jwts.parser().setSigningKey("kkLoginToken").parseClaimsJws(token).getBody();
            String id = kkLoginToken.getId();
            String tokenPassword = (String) kkLoginToken.get("password" + SALT);

            if (userId == Integer.parseInt(id) && !isTokenExpired(kkLoginToken.getExpiration()) && DigestUtil.md5Hex(password + SALT).equals(tokenPassword)) {
                return true;
            }
        } catch (Exception exception) {
            log.error("token验证失败");
            return false;
        }
        return false;
    }

    public static String getUsername(String token) {
        try {
            Claims kkLoginToken = Jwts.parser().setSigningKey("kkLoginToken").parseClaimsJws(token).getBody();
            return kkLoginToken.getSubject();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    public static String createToken(String username, String password, Integer userId) {
        //当前时间
        long now = System.currentTimeMillis();
        //过时时间
        long exp = now + EXPIRE_TIME;
        JwtBuilder builder = Jwts.builder().setId(userId.toString())
                .setSubject(username)
                //设置签发时间
                .setIssuedAt(new Date())
                //密码盐值加密
                .claim("password" + SALT, DigestUtil.md5Hex(password + SALT))
                //设置签名秘钥
                .signWith(SignatureAlgorithm.HS256, "kkLoginToken")
                //设置超时时间
                .setExpiration(new Date(exp));
        return builder.compact();
    }
    
    public static boolean isTokenExpired(Date ExpirationDate) {
        return ExpirationDate.before(new Date());
    }
}

3、JWT过滤器

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return executeLogin(request, response);
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws AuthenticationException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        JWTToken jwtToken = new JWTToken(token);
        getSubject(request, response).login(jwtToken);
        return true;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginAttempt(request, response)) {
            return executeLogin(request, response);
        }
        return false;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) {
        try {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setHeader("Access-control-Allow-Origin", "*");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", "*");
            if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
                httpServletResponse.setStatus(HttpStatus.OK.value());
                return false;
            }
            return super.preHandle(request, response);
        } catch (Exception e) {
            response401(response);
        }
        return false;
    }

    private void response401(ServletResponse resp) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
            //重定向到自定义的异常处理接口
            httpServletResponse.sendRedirect("/common/401");
        } catch (IOException e) {
            throw new RuntimeException("重定向出现异常");
        }
    }
}

4、自定义Realm域

import com.konka.auth.model.bo.common.UserInfo;
import com.konka.auth.service.common.CommonService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
public class ShiroRealm extends AuthorizingRealm {
    @Resource
    private CommonService commonService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 执行授权逻辑
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = JWTUtil.getUsername(principals.toString());
        //查询用户信息
        UserInfo userInfo = commonService.getUserInfo(username);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(userInfo.getRoles());
        simpleAuthorizationInfo.addStringPermissions(userInfo.getPermissions());
        return simpleAuthorizationInfo;
    }

    /**
     * 执行登录认证逻辑
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        try {
            //SimpleAuthenticationInfo中的参数返回
            String token = (String) authenticationToken.getCredentials();
            if (StringUtils.isEmpty(token)) {
                throw new AuthenticationException("缺少token信息");
            }
            String username = JWTUtil.getUsername(token);
            if (StringUtils.isEmpty(username)) {
                throw new AuthenticationException("token无效");
            }
            UserInfo userInfo = commonService.getUserInfo(username);
            //验证token
            if (!JWTUtil.verify(token, userInfo.getId(), userInfo.getPassword())) {
                throw new AuthenticationException("用户名称或者密码错误");
            }
            return new SimpleAuthenticationInfo(token, token, "shiroRealm");
        } catch (Exception e) {
            throw new AuthenticationException("token无效");
        }
    }
}

5、Shiro工具类

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@Configuration
public class ShiroConfig {
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //添加本身的过滤器并取名为jwt
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("common/login");

        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        //全部请求经过JWTFilter
        //访问401和404页面不经过JWTFilter
        filterRuleMap.put("/common/401", "anon");
        filterRuleMap.put("/lua/**", "anon");
        filterRuleMap.put("/error", "anon");
        filterRuleMap.put("/common/login", "anon");
        filterRuleMap.put("/errorResponse", "anon");
        filterRuleMap.put("/swagger-ui.html/**", "anon");
        filterRuleMap.put("/swagger-ui.html", "anon");
        filterRuleMap.put("/webjars/**", "anon");
        filterRuleMap.put("/v2/**", "anon");
        filterRuleMap.put("/swagger-resources/**", "anon");
        //注意shiro过滤链的顺序
        filterRuleMap.put("/**", "jwt");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 建立DefaultWebSecurityManager
     *
     * @param shiroRealm
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(defaultSubjectDAO);
        //关联shiroRealm
        securityManager.setRealm(shiroRealm);
        return securityManager;
    }

    /**
     * ShiroRealm
     */
    @Bean(name = "shiroRealm")
    public ShiroRealm shiroRealm() {
        return new ShiroRealm();
    }


    /**
     * 添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        //强制使用cglib防止重复代理和可能引发代理出错的问题
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

附登录接口、401接口以及shiro的获取用户信息接口(待完善):

CommonController:

import com.alibaba.fastjson.JSONObject;
import com.konka.auth.common.Response;
import com.konka.auth.config.shiro.JWTUtil;
import com.konka.auth.exception.BusinessException;
import com.konka.auth.exception.ExceptionCode;
import com.konka.auth.model.bo.common.UserInfo;
import com.konka.auth.service.common.CommonService;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@RestController
@RequestMapping("/common")
public class CommonController {
    @Resource
    private CommonService commonService;

    @ApiOperation("登录接口")
    @PostMapping(value = "/login", produces = {"application/json;charset=UTF-8"})
    public Response login(@RequestParam("username") String username,
                          @RequestParam("password") String password) {
        try {
            JSONObject jsonObject = new JSONObject();
            //根据用户名称和加密后的密码查询数据库中符合要求的数据
            UserInfo userInfo = commonService.getUserInfo(username, password);
            if (userInfo != null) {
                //登录成功生成一个token返回给前端
                String token = JWTUtil.createToken(username, userInfo.getPassword(), userInfo.getId());

                //返回用户信息和token信息
                jsonObject.put("token", token);
                jsonObject.put("userInfo", userInfo);

                return new Response<>(jsonObject);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(ExceptionCode.System.LOGIN_ERROR_CODE, ExceptionCode.System.LOGIN_ERROR_MSG);
        }
        throw new BusinessException(ExceptionCode.System.LOGIN_ERROR_CODE, ExceptionCode.System.LOGIN_ERROR_MSG);
    }

    @ApiOperation("401接口")
    @GetMapping("/401")
    public Response error() {
        // TODO: 2022/5/24 捕获认证失败重定向接口逻辑
        return new Response("error");
    }
}

 CommonService:

import com.konka.auth.model.bo.common.UserInfo;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
public interface CommonService {
    UserInfo getUserInfo(String username);

    UserInfo getUserInfo(String username, String password);
}

CommonServiceImpl: 

import com.konka.auth.model.bo.common.UserInfo;
import com.konka.auth.service.common.CommonService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhenghaorui
 * @date 2022/5/19
 */
@Slf4j
@Service
public class CommonServiceImpl implements CommonService {
    @Override
    public UserInfo getUserInfo(String username) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUsername("admin");
        userInfo.setPassword("563fc656e1b395f44e22750e52ba6ec3");
        List<String> roles = new ArrayList<>();
        roles.add("超级管理员");
        userInfo.setRoles(roles);
        List<String> permissions = new ArrayList<>();
        permissions.add("增加");
        permissions.add("删除");
        permissions.add("查询");
        permissions.add("修改");
        userInfo.setPermissions(permissions);
        return userInfo;
    }

    @Override
    public UserInfo getUserInfo(String username, String password) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUsername("admin");
        userInfo.setPassword("563fc656e1b395f44e22750e52ba6ec3");
        List<String> roles = new ArrayList<>();
        roles.add("超级管理员");
        userInfo.setRoles(roles);
        List<String> permissions = new ArrayList<>();
        permissions.add("增加");
        permissions.add("删除");
        permissions.add("查询");
        permissions.add("修改");
        userInfo.setPermissions(permissions);
        return userInfo;
    }
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐