SpringBoot集成shiro、jwt进行登录

一、什么是shiro

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 来的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单地解决项目问题就好了。

二、SpringBoot引入shiro的依赖


org.apache.shiro

shiro-spring

1.9.1


com.auth0

java-jwt

3.11.0



io.jsonwebtoken

jjwt

0.9.0

三、引入shiro的配置

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;import org.apache.shiro.mgt.DefaultSubjectDAO;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.mgt.SubjectFactory;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;/** * 描述: * */@Configurationpublic class ShiroConfig {    @Bean(name = "shiroFilter")    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        Map filterMap = new HashMap<>();        //这个地方其实另外两个filter可以不设置,默认就是        //filterMap.put("anon", new AnonymousFilter());        filterMap.put("jwt", new JwtFilter());        //filterMap.put("logout", new LogoutFilter());        shiroFilterFactoryBean.setFilters(filterMap);        shiroFilterFactoryBean.setSecurityManager(securityManager);        shiroFilterFactoryBean.setLoginUrl("/login/loginPage");        shiroFilterFactoryBean.setUnauthorizedUrl("/login/loginPage");        Map filterChainDefinitionMap = new LinkedHashMap<>();        //         filterChainDefinitionMap.put("/login/ssoLogin", "anon");        filterChainDefinitionMap.put("/login/loginPage", "anon");        filterChainDefinitionMap.put("/login/loginCheck","anon");        filterChainDefinitionMap.put("/login/quit","anon");        filterChainDefinitionMap.put("/test2","anon");        filterChainDefinitionMap.put("/test1","anon");        filterChainDefinitionMap.put("/static/**","anon");        filterChainDefinitionMap.put("/favicon.ico","anon");        filterChainDefinitionMap.put("/", "anon");        //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证        filterChainDefinitionMap.put("/**", "jwt");        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);        return shiroFilterFactoryBean;    }    @Bean    public SecurityManager securityManager() {        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();        defaultSecurityManager.setRealm(customRealm());        // 关闭 ShiroDAO 功能        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();        // 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中)        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);        defaultSecurityManager.setSubjectDAO(subjectDAO);        //禁止Subject的getSession方法        defaultSecurityManager.setSubjectFactory(subjectFactory());        return defaultSecurityManager;    }    @Bean    public CustomRealm customRealm() {        return new CustomRealm();    }    @Bean    public SubjectFactory subjectFactory() {        return new JwtDefaultSubjectFactory();    }}

四、配置JwtFilter

import lombok.extern.slf4j.Slf4j;import org.apache.shiro.web.filter.AccessControlFilter;import org.springframework.util.StringUtils;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/* * 自定义一个Filter,用来拦截所有的请求判断是否携带Token * isAccessAllowed()判断是否携带了有效的JwtToken * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录,登录成功允许访问,登录失败拒绝访问 * */@Slf4jpublic class JwtFilter extends AccessControlFilter {    /*     * 1. 返回true,shiro就直接允许访问url     * 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url     * */    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {        return false;    }    /**     * 返回结果为true表明登录通过     */    @Override    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {        //这个地方和前端约定,要求前端将jwtToken放在请求的Header部分        //所以以后发起请求的时候就需要在Header中放一个Authorization,值就是对应的Token        HttpServletRequest request = (HttpServletRequest) servletRequest;        String jwt = request.getHeader("Authorization");        if(StringUtils.isEmpty(jwt)){            jwt=request.getParameter("token");        }             /*        * 此处执行相关的业务逻辑,判断是否允许进行相应的登录        */        JwtToken jwtToken = new JwtToken(jwt);        try {            // 委托 realm 进行登录认证            //所以这个地方最终还是调用JwtRealm进行的认证            getSubject(servletRequest, servletResponse).login(jwtToken);            //也就是subject.login(token)        } catch (Exception e) {            e.printStackTrace();            onLoginFail(servletResponse);            //调用下面的方法向客户端返回错误信息            return false;        }        return true;    }    //登录失败时默认返回 401 状态码    private void onLoginFail(ServletResponse response) throws IOException {        HttpServletResponse httpResponse = (HttpServletResponse) response;        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);        //httpResponse.getWriter().write("login error");        httpResponse.sendRedirect("/login/loginPage");    }}


五、shiro默认是创建会话的,关闭相应的会话

import org.apache.shiro.subject.Subject;

import org.apache.shiro.subject.SubjectContext;

import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

public class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory {

@Override

public Subject createSubject(SubjectContext context) {

// 不创建

session context.setSessionCreationEnabled(false);

return super.createSubject(context);

}

}

六、创建属于自己的JwtToken

import org.apache.shiro.authc.AuthenticationToken;//这个就类似UsernamePasswordTokenpublic class JwtToken implements AuthenticationToken {    private String jwt;    public JwtToken(String jwt) {        this.jwt = jwt;    }    @Override//类似是用户名    public Object getPrincipal() {        return jwt;    }    @Override//类似密码    public Object getCredentials() {        return jwt;    }    //返回的都是jwt}

七、身份验证和权限验证

import com.south.wires.entity.db2.WkcrUser;import com.south.wires.utils.other.JwtUtil;import com.south.wires.utils.other.RedisUtil;import com.south.wires.utils.other.SpringUtil;import lombok.extern.slf4j.Slf4j;import org.apache.shiro.SecurityUtils;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 java.util.HashSet;import java.util.Set;@Slf4jpublic class CustomRealm extends AuthorizingRealm {    /*** 判断token是否是我们的这个jwttoekn */    @Override    public boolean supports(AuthenticationToken token) {        return token instanceof JwtToken;    }    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();        Set stringSet = new HashSet<>();        stringSet.add("user:show");        stringSet.add("user:admin");        info.setStringPermissions(stringSet);        return info;    }    /*** 获取即将需要认证的信息*/    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {        String jwt = (String) authenticationToken.getPrincipal();        if (jwt == null) {            throw new NullPointerException("jwtToken 不允许为空");        }        //判断        JwtUtil jwtUtil = new JwtUtil();      /**自行执行相应的判断逻辑*/        return new SimpleAuthenticationInfo(jwt,jwt,"JwtRealm");    }}


发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章