houweiyu 4 gadi atpakaļ
vecāks
revīzija
acfad90eac

+ 112 - 0
kankan-daikan-core/src/main/java/kankan/daikan/base/shiro/config/ShiroConfig.java

@@ -0,0 +1,112 @@
+package kankan.daikan.base.shiro.config;
+
+
+
+import fdage.back.sdk.base.shiro.enums.LoginType;
+import kankan.daikan.base.shiro.filter.JwtFilter;
+import kankan.daikan.base.shiro.realm.AppJwtRealm;
+import kankan.daikan.base.shiro.realm.UserJwtRealm;
+import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
+import org.apache.shiro.mgt.DefaultSubjectDAO;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+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.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.Filter;
+import java.util.*;
+
+@Configuration
+public class ShiroConfig {
+
+    @Autowired
+    private AppJwtRealm appJwtRealm;
+
+    @Autowired
+    private UserJwtRealm userJwtRealm;
+
+    @Bean(name = "shiroFilter")
+    public ShiroFilterFactoryBean shiroFilter() {
+        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
+        shiroFilterFactoryBean.setSecurityManager(securityManager());
+        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
+        //配置不会被拦截的链接 顺序判断  filterChainDefinitionMap.put("/**", "anon");
+        //自定义过滤器
+        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
+        filterMap.put("app_jwt", new JwtFilter(LoginType.APP.getType()));
+        filterMap.put("user_jwt", new JwtFilter(LoginType.USER.getType()));
+        shiroFilterFactoryBean.setFilters(filterMap);
+        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
+        //TODO:这里需要挪到配置文件中
+        filterChainDefinitionMap.put("/web/user/login", "anon");
+        filterChainDefinitionMap.put("/app/**", "app_jwt");
+        filterChainDefinitionMap.put("/web/**", "user_jwt");
+        //未授权界面;
+        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
+        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
+        return shiroFilterFactoryBean;
+    }
+
+    @Bean(name = "securityManager")
+    public SecurityManager securityManager() {
+        List<Realm> realms = new ArrayList<>();
+        realms.add(appJwtRealm);
+        realms.add(userJwtRealm);
+        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
+        //设置realm.
+        securityManager.setAuthenticator(modularRealmAuthenticator());
+        /*
+         * 关闭shiro自带的session,详情见文档
+         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
+         */
+        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
+        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
+        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
+        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
+        securityManager.setSubjectDAO(subjectDAO);
+        securityManager.setRealms(realms);
+        return securityManager;
+    }
+
+    @Bean
+    public ModularRealmAuthenticator modularRealmAuthenticator() {
+        //自己重写的ShiroModularRealmAuthenticator
+        ShiroModularRealmAuthenticator modularRealmAuthenticator = new ShiroModularRealmAuthenticator();
+        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
+        return modularRealmAuthenticator;
+    }
+
+    /**
+     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
+     *
+     * @return
+     */
+    @Bean
+    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
+        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
+        advisorAutoProxyCreator.setProxyTargetClass(true);
+        return advisorAutoProxyCreator;
+    }
+
+    /**
+     * 开启aop注解支持
+     *
+     * @param securityManager
+     * @return
+     */
+    @Bean
+    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
+        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
+        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
+        return authorizationAttributeSourceAdvisor;
+    }
+
+
+}

+ 50 - 0
kankan-daikan-core/src/main/java/kankan/daikan/base/shiro/config/ShiroModularRealmAuthenticator.java

@@ -0,0 +1,50 @@
+package kankan.daikan.base.shiro.config;
+
+import fdage.back.sdk.base.shiro.token.JWTToken;
+import lombok.extern.log4j.Log4j2;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.realm.Realm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+@Log4j2
+public class ShiroModularRealmAuthenticator extends ModularRealmAuthenticator {
+
+    /**
+     * 这里是给filter来筛选realm使用的
+     * 所有的realm通过Logintype来过滤和匹配
+     * */
+    @Override
+    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
+            throws AuthenticationException {
+        // 判断getRealms()是否返回为空
+        assertRealmsConfigured();
+        // 强制转换回自定义的CustomizedToken
+        JWTToken customizedToken = (JWTToken) authenticationToken;
+        // 登录类型
+        String loginType = customizedToken.getLoginType();
+        // 所有Realm
+        Collection<Realm> realms = getRealms();
+        // 登录类型对应的所有Realm
+        Collection<Realm> typeRealms = new ArrayList<>();
+        for (Realm realm : realms) {
+            //这里通过loginType来匹配参与鉴权的realm
+            if (realm.getName().contains(loginType)){
+                typeRealms.add(realm);
+            }
+        }
+
+        // 判断是单Realm还是多Realm
+        if (typeRealms.size() == 1){
+            return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
+        }
+        else{
+            return doMultiRealmAuthentication(typeRealms, customizedToken);
+        }
+    }
+
+}

+ 43 - 0
kankan-daikan-core/src/main/java/kankan/daikan/base/shiro/exception/JwtAuthenticationException.java

@@ -0,0 +1,43 @@
+package kankan.daikan.base.shiro.exception;
+
+import fdage.back.sdk.base.enums.ResultCodeEnum;
+
+import javax.naming.AuthenticationException;
+
+public class JwtAuthenticationException extends AuthenticationException {
+
+    private static final long serialVersionUID = 2899335020273674736L;
+
+    private Integer code;
+
+    private String msg;
+
+    public JwtAuthenticationException(Integer code, String msg){
+        super(msg);
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public JwtAuthenticationException(ResultCodeEnum resultCodeEnum){
+        super(resultCodeEnum.getDesc());
+        this.code = resultCodeEnum.getCode();
+        this.msg = resultCodeEnum.getDesc();
+    }
+
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+}

+ 121 - 0
kankan-daikan-core/src/main/java/kankan/daikan/base/shiro/filter/JwtFilter.java

@@ -0,0 +1,121 @@
+package kankan.daikan.base.shiro.filter;
+
+import com.alibaba.fastjson.JSONObject;
+import fdage.back.sdk.base.enums.ResultCodeEnum;
+import fdage.back.sdk.base.exception.CommonBaseException;
+import fdage.back.sdk.base.shiro.token.JWTToken;
+import kankan.daikan.base.shiro.exception.JwtAuthenticationException;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+@Log4j2
+public class JwtFilter extends BasicHttpAuthenticationFilter {
+
+    private String loginType;
+    public JwtFilter(String loginType) {
+        this.loginType = loginType;
+    }
+    @Override
+    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
+        return super.isLoginAttempt(request, response);
+    }
+
+
+    /**
+     * 执行登录认证
+     *
+     * @param request
+     * @param response
+     * @param mappedValue
+     * @return
+     */
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+
+
+        try {
+            log.info("Step 1: 接口拦截shiro认证权限");
+            executeLogin(request, response);
+            log.info("Step Final : 用户认证完成");
+            return true;
+        } catch (Exception e) {
+            log.info("认证出现异常:{}" + e);
+            // 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常))
+            Throwable throwable = e.getCause();
+            JSONObject jsonObject = new JSONObject();
+            if (throwable instanceof JwtAuthenticationException) {
+                jsonObject.put("code", ((JwtAuthenticationException) throwable).getCode());
+                jsonObject.put("msg", ((JwtAuthenticationException) throwable).getMsg());
+            } else {
+                jsonObject.put("code", 3004);
+                jsonObject.put("msg", "认证异常,请重新登录");
+            }
+            // 直接返回Response信息
+            this.response(response, jsonObject);
+            return false;
+        }
+    }
+
+
+    /**
+     *
+     */
+    @Override
+    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        String token = httpServletRequest.getHeader("Authorization");
+        if (token == null) {
+            token = httpServletRequest.getHeader("token");
+        }
+        if(StringUtils.isBlank(token)){
+            throw new CommonBaseException(ResultCodeEnum.D101 , "缺失认证数据");
+        }
+        JWTToken jwtToken = new JWTToken(token, loginType);
+        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
+        getSubject(request, response).login(jwtToken);
+        // 如果没有抛出异常则代表登入成功,返回true
+        return true;
+    }
+
+
+    /**
+     * 对跨域提供支持
+     */
+    @Override
+    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+        //这里不做跨域的配置,直接在nginx上做跨域转发
+        return super.preHandle(request, response);
+    }
+
+    /**
+     * 无需转发,直接返回Response信息
+     */
+    private void response(ServletResponse response, JSONObject msg) {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType("application/json; charset=utf-8");
+        PrintWriter out = null;
+        try {
+            out = response.getWriter();
+            out.append(msg.toString());
+            out.flush();
+            out.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+}

+ 65 - 0
kankan-daikan-core/src/main/java/kankan/daikan/base/shiro/realm/AppJwtRealm.java

@@ -0,0 +1,65 @@
+package kankan.daikan.base.shiro.realm;
+
+import fdage.back.sdk.base.shiro.token.JWTToken;
+import kankan.daikan.base.shiro.exception.JwtAuthenticationException;
+import lombok.SneakyThrows;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+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.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+@Log4j2
+@Component
+public class AppJwtRealm extends AuthorizingRealm {
+
+    @Autowired
+    RedisTemplate redisTemplate;
+
+    /**
+     * !!!!!!!这个要重写,否则会报错!!!!!!!
+     * */
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof JWTToken;
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        return new SimpleAuthorizationInfo();
+    }
+
+    @SneakyThrows
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
+        log.info("Step 2:进行用户名正确与否验证");
+        JWTToken jwtToken = (JWTToken) auth;
+        String token = (String) jwtToken.getCredentials();
+        if (StringUtils.isEmpty(token)) {
+            //无token,需要重新登录动作,生成token
+            throw new JwtAuthenticationException(3004 , "请重新登录");
+        } else {
+            //校验token
+            validateToken(token);
+        }
+        return new SimpleAuthenticationInfo(token, token, "jwt_realm");
+    }
+
+
+    private void validateToken(String token) throws JwtAuthenticationException {
+        if(!redisTemplate.hasKey(token)){
+            throw new JwtAuthenticationException(3005 , "认证信息无效,请确认后重试");
+        }
+        log.info("Step 3:验证token完成");
+    }
+
+
+}

+ 106 - 0
kankan-daikan-core/src/main/java/kankan/daikan/base/shiro/realm/UserJwtRealm.java

@@ -0,0 +1,106 @@
+package kankan.daikan.base.shiro.realm;
+
+import com.alibaba.fastjson.JSONObject;
+import fdage.back.sdk.base.enums.ResultCodeEnum;
+import fdage.back.sdk.base.exception.CommonBaseException;
+import fdage.back.sdk.base.shiro.token.JWTToken;
+import fdage.back.sdk.utils.HttpClientUtil;
+import fdage.back.sdk.utils.JwtUtil;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+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.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author abnerhou
+ * @date 2020/5/20 16:41
+ * @desciption
+ */
+@Log4j2
+@Component
+public class UserJwtRealm extends AuthorizingRealm {
+
+
+    @Value("${scenePath}")
+    private String kankanHost;
+
+
+    /**
+     * !!!!!!!这个要重写,否则会报错!!!!!!!
+     */
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof JWTToken;
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        //系统管理员,拥有最高权限
+        //用户权限列表
+        // Set<String> permsSet = new HashSet<String>();
+        //角色列表,TODO:目前只用到了角色纬度,暂不需要细化到权限粒度
+        Set<String> roleSet = new HashSet<String>();
+
+        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
+        info.setRoles(roleSet);
+        return info;
+    }
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
+        log.info("Step 2: Agent进行用户名正确与否验证");
+        JWTToken jwtToken = (JWTToken) auth;
+        String token = (String) jwtToken.getCredentials();
+        if (StringUtils.isEmpty(token)) {
+            //无token,需要重新登录动作,生成token
+            throw new CommonBaseException(3004, "请重新登录");
+        } else {
+
+            validateToken(token);
+
+        }
+        String userName = JwtUtil.getUserName(token);
+        if (StringUtils.isBlank(userName)) {
+            throw new CommonBaseException(ResultCodeEnum.D101, "登录用户非法");
+        }
+        return new SimpleAuthenticationInfo(token, token, getName());
+    }
+
+    private void validateToken(String token) {
+        String url = kankanHost + "api/sso/user/checkToken";
+        log.info("验证token的url:{}", url);
+        Map<String, Object> headers = new HashMap<>();
+        headers.put("token", token);
+        String kankanResult = HttpClientUtil.doPostJsonWithHeader(url, "", headers);
+        //把信息封装为json
+        JSONObject res = JSONObject.parseObject(kankanResult);
+        if (null != res) {
+            log.info("登录态校验接口返回:{}", res.toJSONString());
+            int code = -10;
+            if (res.containsKey("code")) {
+                code = (int) res.get("code");
+                if (code != 0) {
+                    throw new CommonBaseException(ResultCodeEnum.D3002);
+                }
+            } else {
+                throw new CommonBaseException(ResultCodeEnum.D3002);
+            }
+        }
+        log.info("Step 3:验证token完成");
+
+
+    }
+}