最全SpringBoot项目中集成Shiro进行权限管控,作为一个Java程序员

文末

我将这三次阿里面试的题目全部分专题整理出来,并附带上详细的答案解析,生成了一份PDF文档

  • 第一个要分享给大家的就是算法和数据结构

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 第二个就是数据库的高频知识点与性能优化

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 第三个则是并发编程(72个知识点学习)

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

还有更多的Redis、MySQL、JVM、Kafka、微服务、Spring全家桶等学习笔记这里就不一一列举出来

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

UserBean.java

package com.riemann.bean;

import com.fasterxml.jackson.annotation.JsonProperty;

public class UserBean {

private String username;

private String password;

private String role;

private String permission;

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public String getRole() {

return role;

}

public void setRole(String role) {

this.role = role;

}

public String getPermission() {

return permission;

}

public void setPermission(String permission) {

this.permission = permission;

}

}

3、配置 JWT

我们写一个简单的 JWT 加密,校验工具,并且使用用户自己的密码充当加密密钥,这样保证了 token 即使被他人截获也无法破解。并且我们在 token 中附带了 username 信息,并且设置密钥5分钟就会过期。

package com.riemann.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.io.UnsupportedEncodingException;

import java.util.Date;

public class JWTUtil {

// 过期时间5分钟

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

/**

  • 校验token是否正确

  • @param token 密钥

  • @param username 用户名

  • @param secret 用户的密码

  • @return 是否正确

*/

public static boolean verify(String token, String username, String secret) {

try {

Algorithm algorithm = Algorithm.HMAC256(secret);

JWTVerifier verifier = JWT.require(algorithm).withClaim(“username”, username).build();

DecodedJWT jwt = verifier.verify(token);

return true;

} catch (UnsupportedEncodingException e) {

return false;

}

}

/**

  • 获得token中的信息无需secret解密也能获得

  • @param token 密钥

  • @return

*/

public static String getUsername(String token) {

try {

DecodedJWT jwt = JWT.decode(token);

return jwt.getClaim(“username”).asString();

} catch (JWTDecodeException e) {

return null;

}

}

/**

  • 生成签名,5min后过期

  • @param username

  • @param secret

  • @return 加密的token

*/

public static String sign(String username, String secret) {

try {

Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);

Algorithm algorithm = Algorithm.HMAC256(secret);

// 附带username信息

return JWT.create()

.withClaim(“username”, username)

.withExpiresAt(date)

.sign(algorithm);

} catch (UnsupportedEncodingException e) {

return null;

}

}

}

4、构建URL

ResponseBean.java

既然想要实现 restful,那我们要保证每次返回的格式都是相同的,因此我建立了一个 ResponseBean 来统一返回的格式。

package com.riemann.bean;

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;

读者福利

分享一份自己整理好的Java面试手册,还有一些面试题pdf

不要停下自己学习的脚步

字节跳动的面试分享,为了拿下这个offer鬼知道我经历了什么

字节跳动的面试分享,为了拿下这个offer鬼知道我经历了什么

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

权的方法。

代码的执行流程 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;

读者福利

分享一份自己整理好的Java面试手册,还有一些面试题pdf

不要停下自己学习的脚步

[外链图片转存中…(img-HfG1W4Ao-1715583387326)]

[外链图片转存中…(img-zfxFtZxX-1715583387326)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以,以下是一个使用 Shiro 的 Spring Boot 项目的示例: 首先,您需要在 pom.xml 文件添加以下依赖项: ```xml <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.2</version> </dependency> ``` 接下来,您需要在 application.properties 文件配置 Shiro: ```properties # Shiro 配置 shiro: # 登录 URL loginUrl: /login # 登录成功后跳转的 URL successUrl: /index # 未授权 URL unauthorizedUrl: /unauthorized # Shiro 过滤器链配置 filterChainDefinitions: /static/**=anon\n/login=anon\n/logout=logout\n/**=authc ``` 然后,您需要创建一个 Shiro 配置类: ```java @Configuration public class ShiroConfig { /** * 创建 Shiro 过滤器工厂 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 创建 SecurityManager */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); return securityManager; } /** * 创建 Realm */ @Bean public Realm realm() { return new MyRealm(); } /** * 开启 Shiro 注解支持 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } /** * 开启 Shiro AOP 支持 */ @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } } ``` 最后,您需要创建一个 Realm 类来处理身份验证和授权: ```java public class MyRealm extends AuthorizingRealm { /** * 处理授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRole("admin"); authorizationInfo.addStringPermission("user:list"); return authorizationInfo; } /** * 处理身份验证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); if (!"admin".equals(username)) { throw new UnknownAccountException("用户名或密码错误"); } if (!"123456".equals(password)) { throw new IncorrectCredentialsException("用户名或密码错误"); } return new SimpleAuthenticationInfo(username, password, getName()); } } ``` 这就是一个简单的使用 Shiro 的 Spring Boot 项目的示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值