栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springboot shiro jwt实现认证和授权

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springboot shiro jwt实现认证和授权

1.shiro,jwt,oauth2.0是什么?

Shiro:权限框架,可在C/S下运行。 shiro是一套权限管理框架,包括认证、授权等,在使用时直接写相应的接口(小而简单的Shiro就足够)

jwt:是一个鉴权生成加密token的一个名称。

oauth2.0 :一种权限实现标准,是一种安全的授权框架,提供了一套详细的授权机制。用户或应用可以通过公开的或私有的设置,使用第三方认证和授权。

Shiro 有三大核心组件,即 Subject、SecurityManager 和 Realm。

2.依赖引入

            javax.servlet
            javax.servlet-api
            4.0.1
        

        
            org.apache.shiro
            shiro-spring-boot-starter
            1.8.0
        

        
            com.auth0
            java-jwt
            3.18.3
        
3.Jwt的自定义工具类,

主要功能如下:

生成符合Jwt机制的token字符串可以对token字符串进行校验获取token中的用户信息判断token是否过期

package com.lx.shirojwt.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;

public class JwtUtil {
    //指定一个token过期时间(毫秒)
//    private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;  //7天

    private static final long EXPIRE_TIME = 1000 * 60 * 10;

    
    //注意这里的sercet不是密码,而是进行三件套(salt+MD5+1024Hash)处理密码后得到的凭证
    //这里为什么要这么做,在controller中进行说明
    public static String createJwtToken(String username, String secret) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);    //使用密钥进行哈希
        // 附带username信息的token
        return JWT.create()
                .withClaim("username", username)
                .withExpiresAt(date)  //过期时间
                .sign(algorithm);     //签名算法
    }

    
    public static boolean verifyToken(String token, String username, String secret) {
        try {
            //根据密钥生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            //效验TOKEN(其实也就是比较两个token是否相同)
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    
    public static boolean isExpire(String token) {
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getExpiresAt().getTime() < System.currentTimeMillis();
    }
}
4.自定义一个Realm 

自定义的Realm需要继承AuthorizingRealm 类,我们需要重写以下两个方法。

doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。

package com.lx.shirojwt.config;

import com.lx.shirojwt.iservice.IUserService;
import com.lx.shirojwt.model.JwtToken;
import com.lx.shirojwt.model.RolePermModel;
import com.lx.shirojwt.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
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 java.util.List;

@Slf4j
public class CustomerRealm extends AuthorizingRealm {

    @Autowired(required = false)
    IUserService iUserService;

    // 设置realm的名称
    @Override
    public void setName(String name) {
        super.setName("customRealm");
    }

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


    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.debug("开始授权!");
        if (iUserService == null) {
            return null;
        }
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        List rolePermModelList = iUserService.queryRolePermByName(principalCollection.getPrimaryPrincipal().toString());
        rolePermModelList.forEach(row -> {
            simpleAuthorizationInfo.addRole(row.getRoleName());
            simpleAuthorizationInfo.addStringPermissions(row.getPermList());
        });
        return simpleAuthorizationInfo;
    }

    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        log.debug("开始认证!");
        if (iUserService == null) {
            log.warn("IUserService Is Empty");
            return null;
        }
        JwtToken jwtToken = (JwtToken) authenticationToken;
        String token = jwtToken.getCredentials().toString();
        String username = JwtUtil.getUsername(token);
        //toke过期
        if (JwtUtil.isExpire(token)) {
            throw new ExpiredCredentialsException();
        }

        //用户不存在(这个在登录时不会进入,只有在token校验时才有可能进入)
        String secret = iUserService.queryPasswordByName(username);
        if (username == null || secret == null)
            throw new UnknownAccountException();

        //密码错误(这里获取到password,就是3件套处理后的保存到数据库中的凭证,作为密钥)
        if (!JwtUtil.verifyToken(token, username, secret)) {
            throw new IncorrectCredentialsException();
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                username, token, this.getName());
        return simpleAuthenticationInfo;
    }

    
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        super.setCredentialsMatcher(credentialsMatcher);
    }
}
4.自定义拦截的过滤器

自定义的过滤器需要继承AuthenticatingFilter,并且需要重写onAccessDenied方法,重写的方法必须执行SecurityUtils.getSubject().login才会执行我们CustomerRealm中的认证的方法(doGetAuthenticationInfo)

package com.lx.shirojwt.filters;

import com.lx.shirojwt.model.JwtToken;
import com.lx.shirojwt.util.ShiroConstants;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

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

public class AuthFilter extends AuthenticatingFilter {

    
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        String requestToken = getRequestToken((HttpServletRequest) servletRequest);
        JwtToken jwtToken = new JwtToken(requestToken);
        return jwtToken;
    }

    
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equalsIgnoreCase("OPTIONS")) {
            httpServletResponse.setStatus(200);
            return false;
        }
        return super.preHandle(request, response);
    }

    
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        //完成token登入
        //1.检查请求头中是否含有token
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String token = getRequestToken(httpServletRequest);
        //2. 如果客户端没有携带token,拦下请求
        if (null == token || "".equals(token)) {
            responseToken(servletResponse, "Token无效(authorization不存在)", ShiroConstants.UNAUTHORIZED);
            return false;
        }
        //3. 如果有,对进行进行token验证
        return executeLogin(servletRequest, servletResponse);
    }

    
    public boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        JwtToken jwtToken = (JwtToken) createToken(request, response);
        try {
            SecurityUtils.getSubject().login(jwtToken);
        } catch (Exception e) {
            if (e.getClass().getName().equalsIgnoreCase(AuthenticationException.class.getName())) {
                responseToken(response, "Token无效,您无权访问该接口!", ShiroConstants.UNAUTHORIZED);
            } else if (e.getClass().getName().equalsIgnoreCase(UnknownAccountException.class.getName())) {
                responseToken(response, "用户名不存在!", ShiroConstants.OK);
            } else if (e.getClass().getName().equalsIgnoreCase(IncorrectCredentialsException.class.getName())) {
                responseToken(response, "密码错误!", ShiroConstants.OK);
            } else if (e.getClass().getName().equalsIgnoreCase(ExpiredCredentialsException.class.getName())) {
                responseToken(response, "Token已过期!", ShiroConstants.OK);
            } else {
                responseToken(response, "其他错误!", ShiroConstants.OK);
            }
            return false;
        }
        return true;
    }

    
    private String getRequestToken(HttpServletRequest httpRequest) {
        //从header中获取token
        String token = httpRequest.getHeader("authorization");

        //如果header中不存在token,则从参数中获取token
        if (StringUtils.isEmpty(token)) {
            token = httpRequest.getParameter("authorization");
        }
        return token;
    }

    
    private void responseToken(ServletResponse response, String msg, int status) {
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        httpServletResponse.setStatus(status);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try (PrintWriter out = httpServletResponse.getWriter()) {
            out.write(msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Filter Chain 定义说明:

1、一个URL可以配置多个 Filter,使用逗号分隔2、当设置多个过滤器时,全部验证通过,才视为通过3、部分过滤器可指定参数,如 perms,roles

Shiro 内置的 FilterChain

Filter NameClass
anonorg.apache.shiro.web.filter.authc.AnonymousFilter(所有 url 都都可以匿名访问)
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter(需要认证才能进行访问)
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter(需要权限访问)
portorg.apache.shiro.web.filter.authz.PortFilter
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter(需要角色访问)
sslorg.apache.shiro.web.filter.authz.SslFilter
userorg.apache.shiro.web.filter.authc.UserFilter

 

 // /hello/add 必须认证,并且角色admin和addqueryrole,并且是add权限可以访问
 urlFilterMap.put("/hello/add", "authc,roles[admin,addqueryrole],perms[add]");
 // /hello/query 必须认证,并且角色admin或者addqueryrole,并且是add或者query权限可以访问
 // authc,roles,perms是shiro内置的过滤器,roles中括号是并且的关系
 // rolesOr和permsOr 是我们定义的过滤
 urlFilterMap.put("/hello/query", "authc,rolesOr[admin,addqueryrole],permsOr[add,query]");

自定义角色或关系

package com.lx.shirojwt.filters;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.List;
import java.util.stream.Collectors;


public class CustomRolesOrFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        List rolesList = CollectionUtils.asSet(rolesArray).stream().collect(Collectors.toList());
        for (String role : rolesList) {
            if (subject.hasRole(role)) {
                return true;
            }
        }
        return false;
    }
}

自定义权限或关系过滤器

package com.lx.shirojwt.filters;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.List;
import java.util.stream.Collectors;


public class CustomPermissionsFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        List permsList = CollectionUtils.asSet(rolesArray).stream().collect(Collectors.toList());
        for (String perms : permsList) {
            if (subject.isPermitted(perms)) {
                return true;
            }
        }
        return false;
    }
}
5.Shiro配置

IFilterService 是我自定新建的抽象类,用于上层抽象具体的业务,自定义过滤器和资源权限的注入。

package com.lx.shirojwt.config;

import com.lx.shirojwt.controller.ExceptionController;
import com.lx.shirojwt.controller.TokenController;
import com.lx.shirojwt.filters.AuthFilter;
import com.lx.shirojwt.filters.CustomPermissionsFilter;
import com.lx.shirojwt.filters.CustomRolesOrFilter;
import com.lx.shirojwt.iservice.IFilterService;
import org.apache.shiro.mgt.DefaultSessionStorageevaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;

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

@Configuration
public class ShiroAutoConfig {

    @Autowired(required = false)
    IFilterService iFilterService;

    //1创建shiroFilter  负责拦截请求
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map filterMap = new HashMap<>();
        filterMap.put("jwtfilter", new AuthFilter());
        // 角色或关系
        filterMap.put("rolesOr", new CustomRolesOrFilter());
        // 权限或关系
        filterMap.put("permsOr", new CustomPermissionsFilter());
        Map temp = iFilterService == null ? null : iFilterService.createFilterMap();
        if (temp != null) {
            filterMap.putAll(temp);
        }
        //自定义过滤器
        shiroFilterFactoryBean.setFilters(filterMap);
        //配置系统受限资源
        Map urlFilterMap = new linkedHashMap<>();
        urlFilterMap.put("/createtoken", "anon");
        Map tempURL = iFilterService == null ? null : iFilterService.createURLFilterMap();
        if (tempURL != null) {
            urlFilterMap.putAll(tempURL);
        }
        // 对除了anon的过滤器,全部增加WT的过滤器
        urlFilterMap.entrySet().forEach(kv -> {
            if (kv.getValue().contains("anon")) {
                return;
            }
            if (!kv.getValue().contains("jwtfilter")) {
                kv.setValue("jwtfilter," + kv.getValue());
            }
        });
        // 全部接口使用JWT的过滤器
        urlFilterMap.put("
    private DefaultSubjectDAO defaultSubjectDAO() {
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageevaluator defaultSessionStorageevaluator = new DefaultSessionStorageevaluator();
        defaultSessionStorageevaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageevaluator(defaultSessionStorageevaluator);
        return subjectDAO;
    }

    //3.创建自定义realm
    @Bean("realm")
    public Realm getRealm() {
        CustomerRealm customerRealm = new CustomerRealm();
        return customerRealm;
    }


    @Bean
    @ConditionalOnClass(RestController.class)
    public TokenController tokenController() {
        TokenController tokenController = new TokenController();
        return tokenController;
    }

    @Bean
    @ConditionalOnClass(RestController.class)
    public ExceptionController exceptionController() {
        ExceptionController exceptionController = new ExceptionController();
        return exceptionController;
    }

}
package com.lx.shirojwt.iservice;

import javax.servlet.Filter;
import java.util.Collections;
import java.util.Map;

public abstract class IFilterService {

    public Map createFilterMap() {
        return Collections.emptyMap();
    }

    public Map createURLFilterMap() {
        return Collections.emptyMap();
    }
}
package com.lx.shirojwt.iservice;

import com.lx.shirojwt.model.RolePermModel;

import java.util.List;

public interface IUserService {
    String queryPasswordByName(String userName);

    List queryRolePermByName(String userName);
}
6.设置全局异常捕获权限异常
package com.lx.shirojwt.controller;

import com.lx.shirojwt.util.ShiroConstants;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

@RestController
@RestControllerAdvice
public class ExceptionController extends MarkController {

    @GetMapping("/noAuthorization")
    public ResponseEntity noAuthorization(HttpServletRequest request) {
        return new ResponseEntity("无权限访问该接口!", HttpStatus.FORBIDDEN);
    }


    @ExceptionHandler({AuthorizationException.class})
    public ResponseEntity authorizationException(Exception exception, HttpServletRequest request) {
        return new ResponseEntity("无权限访问!" + request.getRequestURI(), HttpStatus.FORBIDDEN);
    }

}
7.启动测试

无token字段:

token过期:

 正常token:

 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/770470.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号