SpringBoot项目中集成Shiro进行权限管控

public class ResponseBean {

// http 状态码

private int code;

// 返回信息

private String msg;

// 返回的数据

private Object data;

public ResponseBean(int code, String msg, Object data) {

this.code = code;

this.msg = msg;

this.data = data;

}

public int getCode() {

return code;

}

public void setCode(int code) {

this.code = code;

}

public String getMsg() {

return msg;

}

public void setMsg(String msg) {

this.msg = msg;

}

public Object getData() {

return data;

}

public void setData(Object data) {

this.data = data;

}

}

自定义异常

为了实现我自己能够手动抛出异常,我自己写了一个 UnauthorizedException.java

package com.riemann.exception;

public class UnauthorizedException extends RuntimeException {

public UnauthorizedException() {

super();

}

public UnauthorizedException(String message) {

super(message);

}

}

URL结构

| URL | 作用 |

| — | — |

| /login | 登入 |

| /article | 所有人都可以访问,但是用户与游客看到的内容不同 |

| /require_auth | 登入的用户才可以进行访问 |

| /require_role | admin的角色用户才可以登入 |

| /require_permission | 拥有view和edit权限的用户才可以访问 |

Controller

package com.riemann.controller;

import com.riemann.bean.ResponseBean;

import com.riemann.bean.UserBean;

import com.riemann.exception.UnauthorizedException;

import com.riemann.service.UserService;

import com.riemann.util.JWTUtil;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.authz.annotation.Logical;

import org.apache.shiro.authz.annotation.RequiresAuthentication;

import org.apache.shiro.authz.annotation.RequiresPermissions;

import org.apache.shiro.authz.annotation.RequiresRoles;

import org.apache.shiro.subject.Subject;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.*;

@RestController

public class WebController {

private static final Logger LOGGER = LoggerFactory.getLogger(WebController.class);

private UserService userService;

@Autowired

public void setService(UserService userService) {

this.userService = userService;

}

@GetMapping(“/login”)

public ResponseBean login(String username, String password) {

UserBean userBean = userService.getUser(username);

if (userBean.getPassword().equals(password)) {

return new ResponseBean(200, “Login success”, JWTUtil.sign(username, password));

} else {

throw new UnauthorizedException();

}

}

@GetMapping(“/article”)

public ResponseBean article() {

Subject subject = SecurityUtils.getSubject();

if (subject.isAuthenticated()) {

return new ResponseBean(200, “You are already logged in”, null);

} else {

return new ResponseBean(200, “You are guest”, null);

}

}

@GetMapping(“/require_auth”)

@RequiresAuthentication

public ResponseBean requireAuth() {

return new ResponseBean(200, “You are authenticated”, null);

}

@GetMapping(“/require_role”)

@RequiresRoles(“admin”)

public ResponseBean requireRole() {

return new ResponseBean(200, “You are visiting require_role”, null);

}

@GetMapping(“/require_permission”)

@RequiresPermissions(logical = Logical.AND, value = {“view”, “edit”})

public ResponseBean requirePermission() {

return new ResponseBean(200, “You are visiting permission require edit,view”, null);

}

@RequestMapping(path = “/401”)

@ResponseStatus(HttpStatus.UNAUTHORIZED)

public ResponseBean unauthorized() {

return new ResponseBean(401, “Unauthorized”, null);

}

}

我这里的/login接口是用的get请求,像这种用户名和密码一般要用post请求,我这里没有去封装接口请求参数哈。

处理框架异常

之前说过 restful 要统一返回的格式,所以我们也要全局处理 Spring Boot 的抛出异常。利用 @RestControllerAdvice 能很好的实现。

package com.riemann.controller;

import com.riemann.bean.ResponseBean;

import com.riemann.exception.UnauthorizedException;

import org.apache.shiro.ShiroException;

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseStatus;

import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice

public class ExceptionController {

// 捕捉shiro的异常

@ResponseStatus(HttpStatus.UNAUTHORIZED)

@ExceptionHandler(ShiroException.class)

public ResponseBean handle401(ShiroException e) {

return new ResponseBean(401, e.getMessage(), null);

}

// 捕捉UnauthorizedException

@ResponseStatus(HttpStatus.UNAUTHORIZED)

@ExceptionHandler(UnauthorizedException.class)

public ResponseBean handle401() {

return new ResponseBean(401, “Unauthorized”, null);

}

// 捕捉其他所有异常

@ExceptionHandler(Exception.class)

@ResponseStatus(HttpStatus.BAD_REQUEST)

public ResponseBean globalException(HttpServletRequest request, Throwable ex) {

return new ResponseBean(getStatus(request).value(), ex.getMessage(), null);

}

private HttpStatus getStatus(HttpServletRequest request) {

Integer statusCode = (Integer) request.getAttribute(“javax.servlet.error.status_code”);

if (statusCode == null) {

return HttpStatus.INTERNAL_SERVER_ERROR;

}

return HttpStatus.valueOf(statusCode);

}

}

5、配置 Shiro

大家可以先看下官方的 Spring-Shiro整合教程,有个初步的了解。不过既然我们用了 Spring-Boot,那我们肯定要争取零配置文件。

实现JWTToken

JWTToken 差不多就是 Shiro 用户名密码的载体。因为我们是前后端分离,服务器无需保存用户状态,所以不需要 RememberMe 这类功能,我们简单的实现下 AuthenticationToken 接口即可。因为 token 自己已经包含了用户名等信息,所以这里我就弄了一个字段。如果你喜欢钻研,可以看看官方的 UsernamePasswordToken 是如何实现的。

package com.riemann.shiro;

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;

}

}

实现Realm

realm 的用于处理用户是否合法的这一块,需要我们自己实现。

package com.riemann.shiro;

import com.riemann.bean.UserBean;

import com.riemann.service.UserService;

import com.riemann.util.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.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.Arrays;

import java.util.HashSet;

import java.util.Set;

@Service

public class MyRealm extends AuthorizingRealm {

private static final Logger LOGGER = LoggerFactory.getLogger(MyRealm.class);

private UserService userService;

@Autowired

public void setUserService(UserService userService) {

this.userService = userService;

}

/**

  • 大坑!,必须重写此方法,不然Shiro会报错

*/

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof JWTToken;

}

/**

  • 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

String username = JWTUtil.getUsername(principalCollection.toString());

UserBean user = userService.getUser(username);

SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

simpleAuthorizationInfo.addRole(user.getRole());

Set permission = new HashSet<>(Arrays.asList(user.getPermission().split(“,”)));

simpleAuthorizationInfo.addStringPermissions(permission);

return simpleAuthorizationInfo;

}

/**

  • 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

String token = (String) authenticationToken.getCredentials();

// 解密获得username,用于和数据库进行对比

String username = JWTUtil.getUsername(token);

if (username == null) {

throw new AuthenticationException(“token invalid”);

}

UserBean userBean = userService.getUser(username);

if (userBean == null) {

throw new AuthenticationException(“User didn’t existed!”);

}

if (! JWTUtil.verify(token, username, userBean.getPassword())) {

throw new AuthenticationException(“Username or password error”);

}

return new SimpleAuthenticationInfo(token, token, “my_realm”);

}

}

doGetAuthenticationInfo() 中用户可以自定义抛出很多异常,详情见文档。

重写 Filter

所有的请求都会先经过 Filter,所以我们继承官方的 BasicHttpAuthenticationFilter ,并且重写鉴权的方法。

代码的执行流程 preHandle -> isAccessAllowed -> isLoginAttempt -> executeLogin

package com.riemann.filter;

import com.riemann.shiro.JWTToken;

import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

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;

public class JWTFilter extends BasicHttpAuthenticationFilter {

private Logger LOGGER = LoggerFactory.getLogger(this.getClass());

/**

  • 判断用户是否想要登入。

  • 检测header里面是否包含Authorization字段即可

*/

@Override

protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {

HttpServletRequest req = (HttpServletRequest) request;

String authorization = req.getHeader(“Authorization”);

return authorization != null;

}

@Override

protected boolean executeLogin(ServletRequest request, ServletResponse response) {

HttpServletRequest httpServletRequest = (HttpServletRequest) request;

String authorization = httpServletRequest.getHeader(“Authorization”);

JWTToken token = new JWTToken(authorization);

// 提交给realm进行登入,如果错误他会抛出异常并被捕获

getSubject(request, response).login(token);

// 如果没有抛出异常则代表登入成功,返回true

return true;

}

@Override

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

if (isLoginAttempt(request, response)) {

try {

executeLogin(request, response);

} catch (Exception e) {

response401(request, response);

}

}

return true;

}

/**

  • 对跨域提供支持

*/

@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().equals(RequestMethod.OPTIONS.name())) {

httpServletResponse.setStatus(HttpStatus.OK.value());

return false;

}

return super.preHandle(request, response);

}

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

-Headers", httpServletRequest.getHeader(“Access-Control-Request-Headers”));

// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态

if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {

httpServletResponse.setStatus(HttpStatus.OK.value());

return false;

}

return super.preHandle(request, response);

}

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-jKZzEAc3-1710843303687)]
[外链图片转存中…(img-AavKLQSg-1710843303688)]
[外链图片转存中…(img-qj9zwTcw-1710843303688)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-kV37wJFF-1710843303688)]

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值