jwt结合shiro实现认证和权限控制,非常详细

前期准备

jwt,我的理解就是可以进行客户端与服务端之间验证的一种技术,取代了之前使用Session来验证的不安全性。为什么不适用Session?

原理是,登录之后客户端和服务端各自保存一个相应的SessionId,每次客户端发起请求的时候就得携带这个SessionId来进行比对

  1. Session在用户请求量大的时候服务器开销太大了
  2. Session不利于搭建服务器的集群(也就是必须访问原本的那个服务器才能获取对应的SessionId)
  3. 小程序,APP不适用session,对于微信小程序,request请求每次都会先请求微信的服务器,再由微信的服务器去访问我们的后端服务器。这样子就不能识别出是哪台浏览器发送的请求。

1.首先,登录的话,不能在通过shiro自己的方法去验证了,因为我们自定义的token需要存储用户使用的token,所以登录的密码验证就不能通过shiro进行验证,而需要我们自己去验证密码的准确性,在登录方法里面可以,在realm认证方法里面也可以.
2.然后,基于token进行权限验证的话,我们请求所有需要认证的接口时候请求头里必须携带token,然后后端进行token认证,判断token是否合法是否过期等等…
3.token的刷新,可以自定义返回code,返回新的token,来进行token刷新工作.
4.token缓存在redis中,可以实现集群token的共享.可以使token的过期删除交给redis

在这里我先不涉及redis,单纯的jwt结合shiro,所以也没有做token的过期处理,登出处理,即使你使用shiro的logout方法登出,这里的token依然没有失效

由于使用jwt是无状态的,不需要使用session,所以需要把session关闭掉。具体的编码过程如下。

个人理解:

  1. jwt负责生成token,取代shiro原生的UsernamePasswordToken;shiro负责认证和权限的校验
  2. 登录逻辑沿用jwt的登录逻辑,即登录时不需要调用shiro的subject.login()方法,只需要校验用户名和密码,然后返回token即可。到了需要进行权限认证时在执行login方法,这里使用的是jwtFilter来进行拦截。

这个工作的流程有,登录,权限控制:

  1. 登录,登录还是做简单的接受请求传递过来的参数,然后和数据库对比,是否一致,一致的话则通过登录,使用jwt生成token,不经过shiro的处理,因为被jwtFilter拦截了。
  2. 认证,认证的话先是被jwtFilter拦截,然后验证token,无异常就继续进入到shiro
    • 如果是只要拥有登录权限的话,那么就经过认证方面就可以了
    • 如果是要控制权限的话,那么就要先认证再授权

有兴趣看源码的话可以去我的项目地址clone下来:

项目地址:https://github.com/HTBWell/shiro-jwt.git

1. 导入依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

2. 编写JwtUtil类

JwtUtil类是用来生成token和验校验解码token的。

步骤:

  1. 设置密钥和token的有效时间
  2. 生成token
  3. 校验token
  4. 获取token的信息
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 com.example.demo.domain.User;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtil {
    //token有效时长
    private static final long EXPIRE=30*60*1000L;
    //token的密钥
    private static final String SECRET="jwt+shiro";


    public static String createToken(User user) throws UnsupportedEncodingException {
        //token过期时间
        Date date=new Date(System.currentTimeMillis()+EXPIRE);

        //jwt的header部分
        Map<String ,Object>map=new HashMap<>();
        map.put("alg","HS256");
        map.put("typ","JWT");

        //使用jwt的api生成token
        String token= JWT.create()
                .withHeader(map)
                .withClaim("username", user.getUsername())//私有声明
                .withExpiresAt(date)//过期时间
                .withIssuedAt(new Date())//签发时间
                .sign(Algorithm.HMAC256(SECRET));//签名
        return token;
    }

    //校验token的有效性,1、token的header和payload是否没改过;2、没有过期
    public static boolean verify(String token){
        try {
            //解密
            JWTVerifier verifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            verifier.verify(token);
            return true;
        }catch (Exception e){
            return false;
        }
    }


    //无需解密也可以获取token的信息
    public static String getUsername(String token){
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }

    }
}

3. 封装token

封装token来替换Shiro原生Token,要实现AuthenticationToken接口

shiro默认supports的是UsernamePasswordToken,而我们现在采用了jwt的方式,所以这里我们自定义一个JwtToken,来完成shiro的supports方法。

import org.apache.shiro.authc.AuthenticationToken;

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;
    }
}

4. 编写JWT的过滤器

这个过滤器是我们的重点,这里我们继承的是Shiro内置的BasicHttpAuthenticationFilter,一个可以内置了可以自动登录方法的的过滤器。也可以继承AuthenticatingFilter。我这里两个都实现了,跑项目的时候只要一个就好了

这个过滤器是要注册到shiro配置里面去的,用来辅助shiro进行过滤处理。所有的请求都会到过滤器来进行处理。

我们需要重写几个方法:

  1. isAccessAllowed:是否允许访问。如果带有 token,则对 token 进行检查,否则直接通过。如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
  2. isLoginAttempt:判断用户是否想要登入。检测 header 里面是否包含 Token 字段。
  3. executeLoginexecuteLogin实际上就是先调用createToken来获取token,这里我们重写了这个方法,就不会自动去调用createToken来获取token,然后调用getSubject方法来获取当前用户再调用login方法来实现登录,这也解释了我们为什么要自定义jwtToken,因为我们不再使用Shiro默认的UsernamePasswordToken了。
  4. preHandle:拦截器的前置拦截,因为我们是前后端分析项目,项目中除了需要跨域全局配置之外,我们再拦截器中也需要提供跨域支持。这样,拦截器才不会在进入Controller之前就被限制了。
  • 继承BasicHttpAuthenticationFilter
import com.example.demo.shiro.JWTToken;
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;
import java.net.URLEncoder;

public class JWTFilter extends BasicHttpAuthenticationFilter {

    //是否允许访问,如果带有 token,则对 token 进行检查,否则直接通过
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //判断请求的请求头是否带上 "Token"
        if (isLoginAttempt(request, response)){
            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            try {
                executeLogin(request, response);
                return true;
            }catch (Exception e){
                //token 错误
                responseError(response,e.getMessage());
            }
        }
        //如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
        return true;
    }
    
    /**
     * 判断用户是否想要登入。
     * 检测 header 里面是否包含 Token 字段
     */
    
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println("isLoginAttempt");
        HttpServletRequest req= (HttpServletRequest) request;
        String token=req.getHeader("Authorization");
        return token!=null;
    }

    /*
     * executeLogin实际上就是先调用createToken来获取token,这里我们重写了这个方法,就不会自动去调用createToken来获取token
     * 然后调用getSubject方法来获取当前用户再调用login方法来实现登录
     * 这也解释了我们为什么要自定义jwtToken,因为我们不再使用Shiro默认的UsernamePasswordToken了。
     * */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("executeLogin");
        HttpServletRequest req= (HttpServletRequest) request;
        String token=req.getHeader("Authorization");
        JWTToken jwt=new JWTToken(token);
        //交给自定义的realm对象去登录,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwt);
        return true;
    }

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

    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        System.out.println("responseError");

        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码,否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/unauthorized/" + message);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

继承AuthenticatingFilter

package com.example.demo.filter;

import com.alibaba.fastjson.JSON;
import com.example.demo.shiro.JWTToken;
import com.example.demo.utils.JWTUtil;
import com.example.demo.utils.Result;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.util.StringUtils;
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;
import java.net.URLEncoder;

public class JWTFilter extends AuthenticatingFilter {

    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        System.out.println("createToken");
        HttpServletRequest request= (HttpServletRequest) servletRequest;
        String token=request.getHeader("Authorization");
        if(StringUtils.isEmpty(token)) {
            return null;
        }
        return new JWTToken(token);
    }



    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        System.out.println("ServletRequest");
        HttpServletRequest request= (HttpServletRequest) servletRequest;
        String token=request.getHeader("Authorization");
        if(StringUtils.isEmpty(token)) {
            System.out.println("ServletRequest::true");
            return true;
        }else {
            try {
                executeLogin(servletRequest, servletResponse);
                return true;
            }catch (Exception e){
                responseError(servletResponse,e.getMessage());
                return false;
            }
        }
    }


    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

        System.out.println("preHandle");

        HttpServletRequest req= (HttpServletRequest) request;
        HttpServletResponse res= (HttpServletResponse) response;

        res.setHeader("Access-control-Allow-Origin",res.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS");
        res.setHeader("Access-Control-Allow-Headers", res.getHeader("Access-Control-Request-Headers"));
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())){
            res.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        System.out.println("responseError");

        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码,否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/unauthorized/" + message);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

5. 编写shiro的自定义Realm对象

AccountRealm是shiro进行登录或者权限校验的逻辑所在,算是核心了,我们需要重写3个方法,分别是

  • supports:为了让realm支持jwt的凭证校验
  • doGetAuthorizationInfo:权限校验
  • doGetAuthenticationInfo:登录认证校验
import com.example.demo.domain.User;
import com.example.demo.service.UserService;
import com.example.demo.utils.JWTUtil;
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.stereotype.Component;

@Component
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    //根据token判断此Authenticator是否使用该realm
    //必须重写不然shiro会报错
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如@RequiresRoles,@RequiresPermissions之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权~~~~~");
        String token=principals.toString();
        String username=JWTUtil.getUsername(token);
        User user=userService.getUser(username);
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //查询数据库来获取用户的角色
        info.addRole(user.getRoles());
        //查询数据库来获取用户的权限
        info.addStringPermission(user.getPermission());
        return info;
    }


    /**
     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可,在需要用户认证和鉴权的时候才会调用
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("认证~~~~~~~");
        String jwt= (String) token.getCredentials();
        String username= null;
        //decode时候出错,可能是token的长度和规定好的不一样了
        try {
            username= JWTUtil.getUsername(jwt);
        }catch (Exception e){
            throw new AuthenticationException("token非法,不是规范的token,可能被篡改了,或者过期了");
        }
        if (!JWTUtil.verify(jwt)||username==null){
            throw new AuthenticationException("token认证失效,token错误或者过期,重新登陆");
        }
        User user=userService.getUser(username);
        if (user==null){
            throw new AuthenticationException("该用户不存在");
        }

        return new SimpleAuthenticationInfo(jwt,jwt,"MyRealm");
    }
}

6. 编写shiro配置文件

配置文件的任务主要有:

  1. 创建defaultWebSecurityManagerBean对象
  2. 创建ShiroFilterFactoryBean来进行 过滤拦截,权限和登录
  3. 关闭session
  4. 添加注解权限开发

springBoot整合jwt与单纯的shiro实现认证有三个不一样的地方,对应下面

  1. 因为不适用Session,所以为了防止会调用getSession()方法而产生错误,需要关闭session
  2. 一些修改,关闭SHiroDao等
  3. 注册JwtFilterShiroFilterFactoryBea
import com.example.demo.filter.JWTFilter;
import com.example.demo.shiro.MyRealm;
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.filter.mgt.DefaultFilterChainManager;
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 javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {


    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        // 设置自定义 realm.
        securityManager.setRealm(myRealm);

        //关闭session
        DefaultSubjectDAO subjectDAO=new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator sessionStorageEvaluator=new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    /**
     * 先走 filter ,然后 filter 如果检测到请求头存在 token,则用 token 去 login,走 Realm 去验证
     */
    @Bean
    public ShiroFilterFactoryBean factory(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap=new LinkedHashMap<>();
        //设置我们自定义的JWT过滤器
        filterMap.put("jwt",new JWTFilter());
        factoryBean.setFilters(filterMap);

        // 设置无权限时跳转的 url;
        factoryBean.setUnauthorizedUrl("/unauthorized/无权限");
        Map<String,String>filterRuleMap=new HashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**","jwt");
        // 访问 /unauthorized/** 不通过JWTFilter
        filterRuleMap.put("/unauthorized/**","anon");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 添加注解支持,如果不加的话很有可能注解失效
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){

        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){

        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

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

7. 捕获shiro异常

因为是前后端分离项目,所以我们需要有一个规范的反馈机制,而不是返回一些没有意义的错误给前端。我在这里做了全局的异常捕获,这里不多解释了。

// 捕捉shiro的异常
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(ShiroException.class)
public Result handle401(ShiroException e) {
    return Result.fail(401, e.getMessage(), null);
}

// 捕捉shiro的异常
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(UnauthenticatedException.class)
public Result handle401(UnauthenticatedException e) {
    return Result.fail(401, "你没有权限访问", null);
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = TokenExpiredException.class)
public Result handler(TokenExpiredException e) throws IOException {
    return Result.fail(HttpStatus.BAD_REQUEST.value(),"token已经过期,请重新登录",null);
}

8. 编写controller

编写controller时,在需要鉴权的地方加上相应的注解就可以了,加注解的接口,会先去判断是否传递了token过来,如果没有传递token直接抛出异常,如果传递了token就会先校验token是否拥有该权限再决定是否允许访问。

1.登录接口

import com.example.demo.domain.User;
import com.example.demo.service.UserService;
import com.example.demo.utils.JWTUtil;
import com.example.demo.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;

import java.io.UnsupportedEncodingException;

@RestController
public class LoginController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result login(@RequestParam String username, @RequestParam String password) throws UnsupportedEncodingException {
        User user=userService.getUserByPass(username, password);
        Assert.notNull(user,"用户名或密码错误");
        String token= JWTUtil.createToken(user);
        return Result.succ(200,"登陆成功",token);
    }

    @RequestMapping(path = "/unauthorized/{message}")
    public Result unauthorized(@PathVariable String message) throws UnsupportedEncodingException {
        return Result.fail(message);
    }
}

2.用户接口

import com.example.demo.service.UserService;
import com.example.demo.utils.Result;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequiresAuthentication
    @GetMapping("/test")
    public Result test(){
        return Result.succ("test");
    }

    @RequiresRoles("admin")
    @GetMapping("/admin")
    public Result admin(){
        return Result.succ("admin");
    }

    @RequiresRoles("vip")
    @PostMapping("/vip")
    public Result vip(){
        return Result.succ("vip");
    }

    @RequiresPermissions("update")
    @PutMapping("/update")
    public Result update(){
        return Result.succ("update");
    }

    @RequiresPermissions("delete")
    @DeleteMapping("/delete")
    public Result delete(){
        return Result.succ("delete");
    }

    @GetMapping("/guest")
    public Result guest(){
        return Result.succ("guest");
    }

}

用户接口主要是为了测试所用的。

9. 测试结果

先看看我们的user表

image-20200729173813436

  • 登录获取token

image-20200729173631692

第一个用户只有user角色和add权限,所以接下来看看请求的情况

  • test接口

image-20200729173955329

image-20200729174130832

  • admin接口

image-20200729174210916

  • update接口

    image-20200729174256863

10. 常用注解接口

之前说了,在需要鉴权的接口方法上面加上注解就可以对该接口进行鉴权了,不需要去config里面一一配置。

1. @RequiresAuthentication

这个注解的作用就是,要求用户登录了之后才可以访问这个接口。加上了这个注解,服务器会先判断传递过来的请求头是否带有token,如果没有直接拒绝访问,如果带有会进行认证部分,认证通过了就可以访问,不通过拒绝访问。

注意注意(敲黑板了!!!):这个注解还没有涉及到鉴权,所以是不会走授权部分的。

2. @RequiresRoles

这个注解是用来鉴别用户的角色的,拥有这个角色的用户才可以访问这个接口。

// 拥有 admin 角色可以访问
@RequiresRoles("admin")
// 拥有 user 或 admin 角色可以访问
@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})

注意注意(又敲黑板了!!!):这个注解已经涉及到鉴权,所以是会走授权部分的。

3. @RequiresPermissions

这个注解是用来鉴别用户的权限的,拥有这个权限的用户才可以访问这个接口。

// 拥有 vip 和 normal 权限可以访问
@RequiresPermissions(logical = Logical.AND, value = {"vip", "normal"})

4. 结合运用

// 拥有 user 或 admin 角色,且拥有 vip 权限可以访问
@GetMapping("/getVipMessage")
@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})
@RequiresPermissions("vip")
public ResultMap getVipMessage() {
    return resultMap.success().code(200).message("成功获得 vip 信息!");
}

当我们写的接口拥有以上的注解时,如果请求没有带有 token 或者带了 token 但权限认证不通过,则会报 UnauthenticatedException 异常.

大家如果想看完整的项目可以到clone下来,跑一下。项目地址https://github.com/HTBWell/shiro-jwt.git

已标记关键词 清除标记
<p> <strong><span style="font-size:24px;">课程简介:</span></strong><br /> <span style="font-size:18px;">历经半个多月的时间,</span><span style="font-size:18px;">Debug</span><span style="font-size:18px;">亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。</span><span></span> </p> <p> <span style="font-size:18px;">其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,</span><span style="font-size:18px;">Debug</span><span style="font-size:18px;">还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程!</span><span></span> </p> <p> <br /> </p> <p> <span style="font-size:24px;"><strong>核心技术栈列表</strong></span><span style="font-size:24px;"><strong>:</strong></span> </p> <p> <br /> </p> <p> <span style="font-size:18px;">值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括</span><span style="font-size:18px;">Spring Boot</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Spring MVC</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Mybatis</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Mybatis-Plus</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Shiro(</span><span style="font-size:18px;">身份认证与资源授权跟会话等等</span><span style="font-size:18px;">)</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Spring AOP</span><span style="font-size:18px;">、防止</span><span style="font-size:18px;">XSS</span><span style="font-size:18px;">攻击、防止</span><span style="font-size:18px;">SQL</span><span style="font-size:18px;">注入攻击、过滤器</span><span style="font-size:18px;">Filter</span><span style="font-size:18px;">、验证码</span><span style="font-size:18px;">Kaptcha</span><span style="font-size:18px;">、热部署插件</span><span style="font-size:18px;">Devtools</span><span style="font-size:18px;">、</span><span style="font-size:18px;">POI</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Vue</span><span style="font-size:18px;">、</span><span style="font-size:18px;">LayUI</span><span style="font-size:18px;">、</span><span style="font-size:18px;">ElementUI</span><span style="font-size:18px;">、</span><span style="font-size:18px;">JQuery</span><span style="font-size:18px;">、</span><span style="font-size:18px;">HTML</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Bootstrap</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Freemarker</span><span style="font-size:18px;">、一键打包部署运行工具</span><span style="font-size:18px;">Wagon</span><span style="font-size:18px;">等等,如下图所示:</span><span></span> </p> <img src="https://img-bss.csdn.net/201908070402564453.png" alt="" /> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:24px;">课程内容与收益</span><span style="font-size:24px;">:</span><span></span> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070403452052.png" alt="" /> </p> <p> <span style="font-size:18px;">总的来说,</span><span style="font-size:18px;">本课程是一门具有很强实践性质的“项目实战”课程,即“</span><span style="font-size:18px;">企业应用员工角色权限管理平台</span><span style="font-size:18px;">”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于</span><span style="font-size:18px;">Shiro</span><span style="font-size:18px;">的资源授权实现员工</span><span style="font-size:18px;">-</span><span style="font-size:18px;">角色</span><span style="font-size:18px;">-</span><span style="font-size:18px;">操作权限、员工</span><span style="font-size:18px;">-</span><span style="font-size:18px;">角色</span><span style="font-size:18px;">-</span><span style="font-size:18px;">数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图:</span> </p> <p> <span></span> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070404285736.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:18px;"><strong>以下为项目整体的运行效果截图:</strong></span> <span></span> </p> <img src="https://img-bss.csdn.net/201908070404538119.png" alt="" /> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405002904.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405078322.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405172638.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405289855.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405404509.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405523495.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p style="text-align:left;"> <span style="font-size:18px;">值得一提的是,在本课程中,</span><span style="font-size:18px;">Debug</span><span style="font-size:18px;">也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:</span><span></span> </p> <img src="https://img-bss.csdn.net/201908070406328884.png" alt="" /> <p> <br /> </p>
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页