Spring Boot实战(二)--集成Shiro

本文档详细介绍了如何在SpringBoot项目中集成Shiro并利用JWT进行权限管理,包括pom.xml配置、JWT工具类、自定义JWTToken、拦截器、自定义Realm、Shiro配置、全局异常处理等关键步骤,以及数据库表结构和测试说明。
摘要由CSDN通过智能技术生成

springboot集成shiro

springboot集成shiro实现权限控制
完整代码地址
项目结构
在这里插入图片描述

1 pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.shiro</groupId>
    <artifactId>springboot-shiro</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <!--mysql连接-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--springboot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--springboot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--code generator-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!-- jwtjar-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2 jwt工具类-JwtUtil

public class JwtUtil {
    /**
     * 过期时间
     */
    private static final long EXPIRE_TIME = 5 * 60 * 1000;

    private static final String CLAIM_NAME = "username";
    /**
     *
     * 生成token
     */
    public static String createToken(String username, String password) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        //加密处理密码
        Algorithm algorithm = Algorithm.HMAC256(password);
        return JWT.create()
                .withClaim(CLAIM_NAME, username)
                .withExpiresAt(date)
                .sign(algorithm);
    }
    /**
     *
     * 验证token
     */

    public static boolean verify(String username, String dbPwd, String token) {
        Algorithm algorithm = Algorithm.HMAC256(dbPwd);
        JWTVerifier jwtVerifier = JWT.require(algorithm)
                .withClaim(CLAIM_NAME, username).build();
        try {
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            return false;
        }
        return true;
    }

    /**
     *
     * 从token中获取用户名
     */

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

3 jwt类-JwtToken

public class JwtToken implements HostAuthenticationToken {

  private String username;
  private char[] password;
  private String host;

  private String token;

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

  public JwtToken(String username, char[] password) {
    this(username, password, (String) null);
  }

  public JwtToken(String username, String password) {
    this(username, (char[]) (null != password ? password.toCharArray() : null), (String) null);
  }

  public JwtToken(String username, char[] password, String host) {
    this.username = username;
    this.password = password;
    this.host = host;
  }

  @Override
  public String getHost() {
    return host;
  }

  @Override
  public Object getPrincipal() {
    return this.getToken();
  }

  @Override
  public Object getCredentials() {
    return this.getToken();
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public char[] getPassword() {
    return password;
  }

  public void setPassword(char[] password) {
    this.password = password;
  }

  public void setHost(String host) {
    this.host = host;
  }

  public String getToken() {
    return token;
  }

  public void setToken(String token) {
    this.token = token;
  }
}

4 自定义拦截器-JwtFilter

public class JwtFilter extends BasicHttpAuthenticationFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtFilter.class);
    private static final String AUTHZ_HEADER = "token";
    private static final String CHARSET = "UTF-8";

    /**
     * 处理未经验证的请求
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) {
        boolean loggedIn = false;
        if (this.isLoginAttempt(request, response)) {
            loggedIn = this.executeLogin(request, response);
        }

        if (!loggedIn) {
            this.sendChallenge(request, response);
        }

        return loggedIn;
    }

    /**
     * 请求是否已经登录(携带token)
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        String authzHeader = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER);
        return authzHeader != null;
    }

    /**
     * 执行登录方法(由自定义realm判断,吃掉异常返回false)
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        String token = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER);
        if (null == token) {
            String msg = "executeLogin method token must not be null";
            throw new IllegalStateException(msg);
        }
        //交给realm判断是否有权限,没权限返回false交给onAccessDenied
        JwtToken jwtToken = new JwtToken(token);
        try {
            this.getSubject(request, response).login(jwtToken);
            return true;
        } catch (AuthenticationException e) {
            return false;
        }
    }

    /**
     * 构建未授权的请求返回,filter层的异常不受exceptionAdvice控制,这里返回401,把返回的json丢到response中
     */
    @Override
    protected boolean sendChallenge(ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        String contentType = "application/json;charset=" + CHARSET;
        httpResponse.setStatus(401);
        httpResponse.setContentType(contentType);
        try {
            String msg = "对不起,您无权限进行操作!";
            RestResponse unauthentication = new RestResponse(msg, false, 401);

            PrintWriter printWriter = httpResponse.getWriter();
            printWriter.append(JSON.toJSONString(unauthentication));
        } catch (IOException e) {
            LOGGER.error("sendChallenge error,can not resolve httpServletResponse");
        }

        return false;
    }

    /**
     * 请求前处理,处理跨域
     */
    @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请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

5 AuthRealm

public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private IUserService iUserService;

    /**
     * 设置realm支持的authenticationToken类型
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return null != token && token instanceof JwtToken;
    }

    /**
     * 登陆认证
     *
     * @param authenticationToken jwtFilter传入的token
     * @return 登陆信息
     * @throws AuthenticationException 未登陆抛出异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        //getCredentials getPrincipal getToken 都是返回jwt生成的token串
        String token = (String) authenticationToken.getCredentials();
        //判断token是否可用
        String username = JwtUtil.getUserName(token);
        if (username == null) {
            throw new AccountException("token invalid");
        }
        User loginUser = iUserService.getByUserName(username);
        if (!JwtUtil.verify(username, loginUser.getPassword(), token)) {
            throw new UnknownAccountException("Username or password error");
        }
        return new SimpleAuthenticationInfo(token, token, getName());
    }

    /**
     * 授权认证
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String token = principalCollection.toString();
        //根据token获取权限授权
        String userName = JwtUtil.getUserName(token);
        User loginUser = iUserService.getByUserName(userName);
        //查询用户角色、权限
        User userWithRoleMenuById = iUserService.getUserWithRoleMenuById(loginUser.getId());
       //设置当前用户的角色
        Set<String> roles=new HashSet() ;
        for (Role role : userWithRoleMenuById.getRoles()) {
            roles.add(role.getName());
        }
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roles);
        //设置当前用户的权限
        Set<String> menus=new HashSet<String>() ;
        for (Menu menu : userWithRoleMenuById.getMenus()) {
            menus.add(menu.getPermission());
        }
        authorizationInfo.setStringPermissions(menus);
        return authorizationInfo;
    }
}

6 ShiroConfig

@Configuration
public class ShiroConfig {

  private static final String JWT_FILTER_NAME = "jwt";

  /**
   * 自定义realm,实现登录授权流程
   */
  @Bean
  public Realm authRealm() {
    return new AuthRealm();
  }

  /**
   * 配置securityManager 管理subject(默认),并把自定义realm交由manager
   */
  @Bean
  public DefaultSecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(authRealm());
    //非web关闭sessionManager(官网有介绍)
    DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
    DefaultSessionStorageEvaluator storageEvaluator = new DefaultSessionStorageEvaluator();
    storageEvaluator.setSessionStorageEnabled(false);
    defaultSubjectDAO.setSessionStorageEvaluator(storageEvaluator);
    securityManager.setSubjectDAO(defaultSubjectDAO);

    return securityManager;
  }

  /**
   * 拦截链
   */
  @Bean
  public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    shiroFilterFactoryBean.setFilters(filterMap());
    shiroFilterFactoryBean.setFilterChainDefinitionMap(definitionMap());

    return shiroFilterFactoryBean;
  }

  /**
   * 自定义拦截器,处理所有请求
   */
  private Map<String, Filter> filterMap() {
    Map<String, Filter> filterMap = new HashMap<>();
    filterMap.put(JWT_FILTER_NAME, new JwtFilter());
    return filterMap;
  }

  /**
   * url拦截规则
   */
  private Map<String, String> definitionMap() {
    Map<String, String> definitionMap = new HashMap<>();
    definitionMap.put("/login", "anon");
    definitionMap.put("/**", JWT_FILTER_NAME);
    return definitionMap;
  }

  /**
   * 开启注解
   */
  @Bean
  @DependsOn("lifecycleBeanPostProcessor")
  public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    // 强制使用cglib代理,防止和aop冲突
    defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    return defaultAdvisorAutoProxyCreator;
  }

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

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

7 全局异常处理

@RestControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler({UnauthenticatedException.class, AuthenticationException.class})
    public RestResponse unauthenticatedException() {
        RestResponse builder = new RestResponse("对不起,您还未登录!",false,403);
        return builder;
    }

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseBody
    public RestResponse unauthorizedException() {
        RestResponse builder = new RestResponse("对不起,您没权限操作!",false,401);
        return builder;
    }

    @ExceptionHandler(UnknownAccountException.class)
    @ResponseBody
    public RestResponse unknownAccountException() {
        RestResponse builder = new RestResponse("登陆失败,用户名或密码错误!",false,403);
        return builder;
    }
}

8 OAuthConstant

public interface OAuthConstant {

    interface Roles {
        String USER = "user";
        String ADMIN = "admin";
    }

    interface Permissions {
        String SELECT = "select";
        String INSERT = "insert";

    }

    interface Code {
        int OK = 200;
        int UNAUTHENTICATED = 401;
        int UNAUTHORIZED = 403;
        int NOT_FOUND = 404;
        int SERVER_ERROR = 500;
        int BUSINESS_ERROR = 600;
    }
}

9 RestResponse

@Data
public class RestResponse {
    private String msg;
    private boolean success;
    private Object data;
    private Integer code;

    public RestResponse(String msg, boolean success, Object data, Integer code) {
        this.msg = msg;
        this.success = success;
        this.data = data;
        this.code = code;
    }

    public RestResponse(String msg, boolean success, Integer code) {
        this.msg = msg;
        this.success = success;
        this.code = code;
    }
}

10 登录controller

@RestController
public class SysUserController {
    @Autowired
    private ISysUserService loginUserService;
    @PostMapping("/login")
    public RestResponse login(String username,String password) {

        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            RestResponse builder = new RestResponse("账号和密码不能为空!",false,403);

            return builder;
        }
        SysUser user = loginUserService.findByUserName(username);

        if (null == user || !password.equals(user.getPassword())) {
            throw new UnknownAccountException("用户名和密码错误");
        }
        String msg = "登录成功,请妥善保管您的token,有效期5分钟!";
        return new RestResponse(msg,true, JwtUtils.createToken(username, password),200);
    }

}

11 资源controller

@RestController
public class RestApiController {
  @GetMapping("/find")
  @RequiresPermissions(OAuthConstant.Permissions.SELECT)
  public RestResponse find() {
    return new RestResponse("find success",true,"find success",200);
  }

  @GetMapping("/list")
  @RequiresRoles(OAuthConstant.Roles.USER)
  public RestResponse list() {
    return  new RestResponse("list success",true,"list success",200);
  }
}

12 sql



SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
  `permission` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '查询', 'select');

-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `menu_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `menu_role_ibfk_1`(`menu_id`) USING BTREE,
  INDEX `menu_role_ibfk_2`(`role_id`) USING BTREE,
  CONSTRAINT `menu_role_ibfk_1` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `menu_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES (1, 1, 1);

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'user');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'hrID',
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '张三', 'admin', 'admin');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `user_role_ibfk_1`(`user_id`) USING BTREE,
  INDEX `user_role_ibfk_2`(`role_id`) USING BTREE,
  CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);

SET FOREIGN_KEY_CHECKS = 1;

13 测试



在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值