Springboot +Shiro +VUE 前后端分离式权限管理系统

news/2023/6/7 23:44:05

前言

前后端分离,一般都是通过token实现,本项目也是一样;用户登录时,生成token及 token过期时间,token与用户是一一对应关系,调用接口的时候,把token放到header或 请求参数中,服务端就知道是谁在调用接口。

代码已上传到Git:

后台代码:https://github.com/FENGZHIJIE1998/shiro-auth

前端代码:https://github.com/FENGZHIJIE1998/shiro-vue

觉得好用的记得点个Star哦

原文链接:https://blog.csdn.net/weixin_42236404/article/details/89319359

第一步:新建工程

第二步:准备好要用的包包和类类

第三步:编写登陆入口

第四步:编写ShiroService中的方法

第五步:编写ShiroConfig类

第六步:实现自定义的AuthenticationToken。

第七步:编写自己的Realm

第八步:实现自定义AuthenticatingFilter。

第九步:详解校验流程

看看效果

第一步:新建工程

pom文件application.yml巴拉巴拉这里省略,这里贴出需要用到的依赖:

        <!--starter--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--  test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--validation--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--JPA--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!--JDBC--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><!--mysql-connector--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- druid-spring-boot-starter --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><!-- swagger --><dependency><groupId>com.spring4all</groupId><artifactId>swagger-spring-boot-starter</artifactId><version>1.8.0.RELEASE</version></dependency><!-- knife4j --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.2</version></dependency><!-- commons-lang --><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency>

第二步:准备好要用的包包和类类

第三步:编写登陆入口

为了方便这里不做密码加盐加密

/*** @Author 大誌* @Date 2019/3/30 22:04* @Version 1.0*/
@RestController
public class ShiroController {private final ShiroService shiroService;public ShiroController(ShiroService shiroService) {this.shiroService = shiroService;}/*** 登录*/@ApiOperation(value = "登陆", notes = "参数:用户名 密码")@PostMapping("/sys/login")public Map<String, Object> login(@RequestBody @Validated LoginDTO loginDTO, BindingResult bindingResult) {Map<String, Object> result = new HashMap<>();if (bindingResult.hasErrors()) {result.put("status", 400);result.put("msg", bindingResult.getFieldError().getDefaultMessage());return result;}String username = loginDTO.getUsername();String password = loginDTO.getPassword();//用户信息User user = shiroService.findByUsername(username);//账号不存在、密码错误if (user == null || !user.getPassword().equals(password)) {result.put("status", 400);result.put("msg", "账号或密码有误");} else {//生成token,并保存到数据库result = shiroService.createToken(user.getUserId());result.put("status", 200);result.put("msg", "登陆成功");}return result;}/*** 退出*/@ApiOperation(value = "登出", notes = "参数:token")@PostMapping("/sys/logout")public Map<String, Object> logout(@RequestHeader("token")String token) {Map<String, Object> result = new HashMap<>();shiroService.logout(token);result.put("status", 200);result.put("msg", "您已安全退出系统");return result;}
}

第四步:编写ShiroService中的方法

主要是生成一个token返回给前端。

/*** @Author 大誌* @Date 2019/3/30 22:18* @Version 1.0*/
@Service
public class ShiroServiceImpl implements ShiroService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate SysTokenRepository sysTokenRepository;/*** 根据username查找用户** @param username* @return User*/@Overridepublic User findByUsername(String username) {User user = userRepository.findByUsername(username);return user;}//12小时后过期private final static int EXPIRE = 3600 * 12;@Override/*** 生成一个token*@param  [userId]*@return Result*/public Map<String, Object> createToken(Integer userId) {Map<String, Object> result = new HashMap<>();//生成一个tokenString token = TokenGenerator.generateValue();//当前时间Date now = new Date();//过期时间Date expireTime = new Date(now.getTime() + EXPIRE * 1000);//判断是否生成过tokenSysToken tokenEntity = sysTokenRepository.findByUserId(userId);if (tokenEntity == null) {tokenEntity = new SysToken();tokenEntity.setUserId(userId);tokenEntity.setToken(token);tokenEntity.setUpdateTime(now);tokenEntity.setExpireTime(expireTime);//保存tokensysTokenRepository.save(tokenEntity);} else {tokenEntity.setToken(token);tokenEntity.setUpdateTime(now);tokenEntity.setExpireTime(expireTime);//更新tokensysTokenRepository.save(tokenEntity);}result.put("token", token);result.put("expire", EXPIRE);return result;}@Overridepublic void logout(String token) {SysToken byToken = findByToken(token);//生成一个tokentoken = TokenGenerator.generateValue();//修改tokenSysToken tokenEntity = new SysToken();tokenEntity.setUserId(byToken.getUserId());tokenEntity.setToken(token);sysTokenRepository.save(tokenEntity);}@Overridepublic SysToken findByToken(String accessToken) {return sysTokenRepository.findByToken(accessToken);}@Overridepublic User findByUserId(Integer userId) {return userRepository.findByUserId(userId);}
}

第五步:编写ShiroConfig类

/*** @Author 大誌* @Date 2019/3/30 21:50* @Version 1.0*/
@Configuration
public class ShiroConfig {@Bean("securityManager")public SecurityManager securityManager(AuthRealm authRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(authRealm);securityManager.setRememberMeManager(null);return securityManager;}@Bean("shiroFilter")public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);//oauth过滤Map<String, Filter> filters = new HashMap<>();filters.put("auth", new AuthFilter());shiroFilter.setFilters(filters);Map<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/webjars/**", "anon");filterMap.put("/druid/**", "anon");filterMap.put("/sys/login", "anon");filterMap.put("/swagger/**", "anon");filterMap.put("/v2/api-docs", "anon");filterMap.put("/swagger-ui.html", "anon");filterMap.put("/swagger-resources/**", "anon");filterMap.put("/**", "auth");shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}@Bean("lifecycleBeanPostProcessor")public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}
}

第六步:实现自定义的AuthenticationToken。

阅读AuthenticatingFilter抽象类中executeLogin方法,我们发现调用 了subject.login(token),这是shiro的登录方法,且需要token参数,我们自定义 AuthToken类,只要实现AuthenticationToken接口,就可以了。

 //AuthenticatingFilter中的executeLogin()
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {AuthenticationToken token = createToken(request, response);if (token == null) {String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +"must be created in order to execute a login attempt.";throw new IllegalStateException(msg);}try {Subject subject = getSubject(request, response);//重点!subject.login(token);return onLoginSuccess(token, subject, request, response);} catch (AuthenticationException e) {return onLoginFailure(token, e, request, response);}}
/*** 自定义AuthenticationToken类* @Author 大誌* @Date 2019/3/31 10:58* @Version 1.0*/
public class AuthToken extends UsernamePasswordToken{private String token;public AuthToken(String token) {this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

这里我实现的时候出现了Token不匹配的Bug。DeBug下可以查到源头是代码是用UsernamePasswordToken.class和我自定义的AuthToken.class配对。按道理应该是true,却返回了false...于是我就把自定义的AuthToken不实现AuthenticationToken,转为继承UsernamePasswordToken,就可以了。(renren-fast中却可以,可能是版本的问题)

2020/4/27修改: 为了避免误导,将上诉代码 AuthenticationToken 修改为 UsernamePasswordToken,并且走了一下源码,发现这个getAuthenticationTokenClass()实际上获取到的是UsernamePasswordToken.class

再回头看看renren-fast中的源码,原来他重写了supports方法!

第七步:编写自己的Realm

发起请求时,接受传过来的token后,如何保证token有效及用户权限呢?调用接口时,接受传过来的token后,如何保证token有效及用户权限呢?其实,Shiro提供了AuthorizingRealm以及AuthenticatingFilter抽象类,继承AuthorizingRealm和AuthenticatingFilter抽象类重写方法即可

/*** @Author 大誌* @Date 2019/3/30 21:38* @Version 1.0*/
@Component
public class AuthRealm extends AuthorizingRealm {@Autowiredprivate ShiroService shiroService;@Override/*** 授权 获取用户的角色和权限*@param  [principals]*@return org.apache.shiro.authz.AuthorizationInfo*/protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//1. 从 PrincipalCollection 中来获取登录用户的信息User user = (User) principals.getPrimaryPrincipal();//Integer userId = user.getUserId();//2.添加角色和权限SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();for (Role role : user.getRoles()) {//2.1添加角色simpleAuthorizationInfo.addRole(role.getRoleName());for (Permission permission : role.getPermissions()) {//2.1.1添加权限simpleAuthorizationInfo.addStringPermission(permission.getPermission());}}return simpleAuthorizationInfo;}@Override/*** 认证 判断token的有效性*@param  [token]*@return org.apache.shiro.authc.AuthenticationInfo*/protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//获取token,既前端传入的tokenString accessToken = (String) token.getPrincipal();//1. 根据accessToken,查询用户信息SysToken tokenEntity = shiroService.findByToken(accessToken);//2. token失效if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {throw new IncorrectCredentialsException("token失效,请重新登录");}//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录User user = shiroService.findByUserId(tokenEntity.getUserId());//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常if (user == null) {throw new UnknownAccountException("用户不存在!");}//5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfoSimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, this.getName());return info;}
}

第八步:实现自定义AuthenticatingFilter。

/*** Shiro自定义auth过滤器** @Author 大誌* @Date 2019/3/31 10:38* @Version 1.0*/
@Component
public class AuthFilter extends AuthenticatingFilter {// 定义jackson对象private static final ObjectMapper MAPPER = new ObjectMapper();/*** 生成自定义token** @param request* @param response* @return* @throws Exception*/@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {//获取请求tokenString token = TokenUtil.getRequestToken((HttpServletRequest) request);return new AuthToken(token);}/*** 步骤1.所有请求全部拒绝访问** @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {return true;}return false;}/*** 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法** @param request* @param response* @return* @throws Exception*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {//获取请求token,如果token不存在,直接返回String token = TokenUtil.getRequestToken((HttpServletRequest) request);if (StringUtils.isBlank(token)) {HttpServletResponse httpResponse = (HttpServletResponse) response;httpResponse.setHeader("Access-Control-Allow-Credentials", "true");httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());httpResponse.setCharacterEncoding("UTF-8");Map<String, Object> result = new HashMap<>();result.put("status", 400);result.put("msg", "请先登录");String json = MAPPER.writeValueAsString(result);httpResponse.getWriter().print(json);return false;}return executeLogin(request, response);}/*** token失效时候调用*/@Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {HttpServletResponse httpResponse = (HttpServletResponse) response;httpResponse.setContentType("application/json;charset=utf-8");httpResponse.setHeader("Access-Control-Allow-Credentials", "true");httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());httpResponse.setCharacterEncoding("UTF-8");try {//处理登录失败的异常Throwable throwable = e.getCause() == null ? e : e.getCause();Map<String, Object> result = new HashMap<>();result.put("status", 400);result.put("msg", "登录凭证已失效,请重新登录");String json = MAPPER.writeValueAsString(result);httpResponse.getWriter().print(json);} catch (IOException e1) {}return false;}}

第九步:详解校验流程

先给你们上一个超级详细的流程图。

接着我们打上断点按照代码走走,可能会有点啰嗦。

1. 前端发起请求首先会进入AuthFilter的 isAccessAllowed(),除了OPTION方法,其余都拦截。

2. 拦截之后进入AuthFilter的onAccessDenied(),这里获取token后判断token是否isBlank。如果是,代表请求未携带token,直接默认返回400,未登录给前端,流程就结束了。如果携带了token则进入第三步,继续流程。

3. 接着进入AuthFilter的createToken,这里生成我们自定义的AuthToken对象。

4. 接着就会来到AuthRealm中的doGetAuthenticationInfo(),在这个方法中继续token的有效性校验,例如过期、和数据库的token对不上(用户已退出)的情况。如果校验失败,进入第5步,否则进入第6步。

5. token失效后回到AuthFilter中的onLoginFailure(),返回400以及msg,流程结束。

6. Token校验成功后进入AuthRealm的doGetAuthorizationInfo(),进行获取当前用户拥有的权限,之后底层代码会进行权限验证。如果用户有权限则会进入请求方法,否则抛出异常。到这一步校验过程就结束了。

看看效果

终于熬完上面的步骤了,这时候总体的架构已经确立好了,下面让我们来看看效果如何

DTO

/*** 登录传输类*/
@Data
public class LoginDTO {@NotBlank(message = "用户名不能为空")private String username;@NotBlank(message = "密码不能为空")private String password;
}

实体类

@Getter
@Setter
@Entity
public class User {@Idprivate Integer userId;private String username;private String password;@ManyToMany(fetch = FetchType.EAGER)@JoinTable(name = "user_role",joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "userId")},inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "roleId")})private Set<Role> roles;}@Getter
@Setter
@Entity
public class Role {@Idprivate Integer roleId;private String roleName;@ManyToMany(fetch = FetchType.EAGER)@JoinTable(name = "role_permission",joinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "roleId")},inverseJoinColumns = {@JoinColumn(name = "PERMISSION_ID", referencedColumnName = "permissionId")})private Set<Permission> permissions;
}@Getter
@Setter
@Entity
public class Permission {@Idprivate Integer permissionId;private String permissionName;private String permission;
}@Getter
@Setter
@Entity
public class SysToken{@Idprivate Integer userId;private String token;private Date expireTime;private Date updateTime
}

以及给实体类附上权限:

我定义了三个用户

用户    角色    权限
Jack    SVIP    select;save;delete;update
Rose    VIP    select;save;update
Paul    P    select
/*
Navicat MySQL Data Transfer
Source Server         : localhost
Source Server Version : 50549
Source Host           : localhost:3306
Source Database       : shiro
Target Server Type    : MYSQL
Target Server Version : 50549
File Encoding         : 65001
Date: 2019-04-07 17:06:36
*/SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (`permission_id` int(11) NOT NULL,`permission` varchar(255) DEFAULT NULL,`permission_name` varchar(255) DEFAULT NULL,PRIMARY KEY (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES ('1', 'select', '查看');
INSERT INTO `permission` VALUES ('2', 'update', '更新');
INSERT INTO `permission` VALUES ('3', 'delete', '删除');
INSERT INTO `permission` VALUES ('4', 'save', '新增');-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (`role_id` int(11) NOT NULL,`role_name` varchar(255) DEFAULT NULL,PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'svip');
INSERT INTO `role` VALUES ('2', 'vip');
INSERT INTO `role` VALUES ('3', 'p');-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (`role_id` int(11) NOT NULL,`permission_id` int(11) NOT NULL,PRIMARY KEY (`role_id`,`permission_id`),KEY `FKf8yllw1ecvwqy3ehyxawqa1qp` (`permission_id`),CONSTRAINT `FKa6jx8n8xkesmjmv6jqug6bg68` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`),CONSTRAINT `FKf8yllw1ecvwqy3ehyxawqa1qp` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES ('1', '1');
INSERT INTO `role_permission` VALUES ('2', '1');
INSERT INTO `role_permission` VALUES ('3', '1');
INSERT INTO `role_permission` VALUES ('1', '2');
INSERT INTO `role_permission` VALUES ('2', '2');
INSERT INTO `role_permission` VALUES ('1', '3');
INSERT INTO `role_permission` VALUES ('1', '4');
INSERT INTO `role_permission` VALUES ('2', '4');-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`user_id` int(11) NOT NULL,`password` varchar(255) DEFAULT NULL,`username` varchar(255) DEFAULT NULL,PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '123', 'Jack');
INSERT INTO `user` VALUES ('2', '123', 'Rose');
INSERT INTO `user` VALUES ('3', '123', 'Paul');-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (`user_id` int(11) NOT NULL,`role_id` int(11) NOT NULL,PRIMARY KEY (`user_id`,`role_id`),KEY `FKa68196081fvovjhkek5m97n3y` (`role_id`),CONSTRAINT `FK859n2jvi8ivhui0rl0esws6o` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`),CONSTRAINT `FKa68196081fvovjhkek5m97n3y` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for sys_token
-- ----------------------------
CREATE TABLE `sys_token` (`user_id` int(11) NOT NULL,`expire_time` datetime DEFAULT NULL,`token` varchar(255) DEFAULT NULL,`update_time` datetime DEFAULT NULL,PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('2', '2');
INSERT INTO `user_role` VALUES ('3', '3');

测试类:因为我是用Swagger来测试,所以为了方便就直接传递token参数。具体开发时候可由前端把接收到的token放入Header。

/*** @Author 大誌* @Date 2019/4/7 15:20* @Version 1.0*/
@RestController
public class TestController {@RequiresPermissions({"save"}) //没有的话 AuthorizationException@PostMapping("/save")public Map<String, Object> save(String token) {System.out.println("save");Map<String, Object> map = new HashMap<String, Object>();map.put("status", 200);map.put("msg", "当前用户有save的权力");return map;}@RequiresPermissions({"delete"}) //没有的话 AuthorizationException@DeleteMapping("/delete")public Map<String, Object> delete(String token) {System.out.println("delete");Map<String, Object> map = new HashMap<String, Object>();map.put("status", 200);map.put("msg", "当前用户有delete的权力");return map;}@RequiresPermissions({"update"}) //没有的话 AuthorizationException@PutMapping("update")public Map<String, Object> update(String token) {System.out.println("update");Map<String, Object> map = new HashMap<String, Object>();map.put("status", 200);map.put("msg", "当前用户有update的权力");return map;}@RequiresPermissions({"select"}) //没有的话 AuthorizationException@GetMapping("select")public Map<String, Object> select(String token, HttpSession session) {System.out.println("select");Map<String, Object> map = new HashMap<String, Object>();map.put("status", 200);map.put("msg", "当前用户有select的权力");return map;}@RequiresRoles({"vip"}) //没有的话 AuthorizationException@GetMapping("/vip")public Map<String, Object> vip(String token) {System.out.println("vip");Map<String, Object> map = new HashMap<String, Object>();map.put("status", 200);map.put("msg", "当前用户有VIP角色");return map;}@RequiresRoles({"svip"}) //没有的话 AuthorizationException@GetMapping("/svip")public Map<String, Object> svip(String token) {System.out.println("svip");Map<String, Object> map = new HashMap<String, Object>();map.put("status", 200);map.put("msg", "当前用户有SVIP角色");return map;}@RequiresRoles({"p"}) //没有的话 AuthorizationException@GetMapping("/p")public Map<String, Object> p(String token) {System.out.println("p");Map<String, Object> map = new HashMap<String, Object>();map.put("status", 200);map.put("msg", "当前用户有P角色");return map;}
}

ExceptionHandler 异常处理器,用于捕获无权限时候的异常

@ControllerAdvice
public class MyExceptionHandler {@ExceptionHandler(value = AuthorizationException.class)@ResponseBodypublic Map<String, String> handleException(AuthorizationException e) {//e.printStackTrace();Map<String, String> result = new HashMap<String, String>();result.put("status", "400");//获取错误中中括号的内容String message = e.getMessage();String msg=message.substring(message.indexOf("[")+1,message.indexOf("]"));//判断是角色错误还是权限错误if (message.contains("role")) {result.put("msg", "对不起,您没有" + msg + "角色");} else if (message.contains("permission")) {result.put("msg", "对不起,您没有" + msg + "权限");} else {result.put("msg", "对不起,您的权限有误");}return result;}
}

启动项目来看看效果: 访问 localhost:9090/shiro/doc.html

登陆失败:

登陆成功:

登录成功后会返回token,记得带上token访问以下接口

有某个角色时候:

没有某个角色的时候:

有某个权力时候:

没有某个权力的时候:

退出系统

原本的token就失效了,我们再访问原本可以访问的接口看看

至此就已经进入尾声了

2020/3/27 新编写了VUE+Element前端页面

正常访问:

非法访问:

重点:当未登录时候访问项目内部页面,由前端控制路由返回登录页,并不会出现可恶的login.jsp,这里我们故意改变数据库token来展示效果。

总结

至于最后没有权利或角色返回的json字符串是因为他抛出AuthorizationException。可以自定义全局异常处理器进行处理。通过这种token达到即可达到前后端分离开发。各位客官,点个赞吧qaq。

2019/11/26日修改:在后续开发中,发现shiro如果使用ShiroConfig中shiroFiltet的map进行权限或角色拦截,会出现只走登陆认证,不走授权认证的情况。这是个巨坑!后续再写一篇文章深究一下。解决方法:使用注解@RequiresRoles() 以及@RequiresPermissions()进行权限和角色拦截

 @Bean("shiroFilter")public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);//自定义过滤(关键)Map<String, Filter> filters = new HashMap<>();filters.put("auth", new AuthFilter());shiroFilter.setFilters(filters);Map<String, String> filterMap = new LinkedHashMap<>();//主要是这部分: 不要用这种方法,最好用注解的方法filterMap.put("/add", "roles[admin]");filterMap.put("/list", "roles[admin,user]");filterMap.put("/delete", "perms[admin:delete]");filterMap.put("/**", "auth");shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}

2020/3/25补充,修改了部分不符合规范的代码,添加了全局异常捕获器。同时补充了校验流程。同时提示两句,因为token频繁在客户端和服务器端传输,因此可能会造成token劫持攻击(既黑客捕获你的token之后就可以代替你为所欲为),如果对这方面有安全隐患的担忧,可以采取每访问一次接口,更新一次token。并且我这里处于方便的原因是采用了mysql存储token,具体开发中应该用redis缓存来存储。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-4565213.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

图谱相关技术在风控反作弊中的应用和探索

‍‍ 导读&#xff1a;互联网黑产不断发展壮大&#xff0c;作弊模式逐渐变得规模化、产业化&#xff0c;团伙作弊行为日益猖獗。为了进一步提升百度账号的安全和用户体验&#xff0c;维护公司核心利益&#xff0c;百度账号安全策略团队结合自身在账号安全领域的优势&#xff0c…

【洞察】152号令,重量级行业信息技术法规

【洞察】152号令&#xff0c;重量级行业信息技术法规 普华永道中国 今天 中国证券监督管理委员会于2018年12月19日出台了《证券基金经营机构信息技术管理办法》&#xff08;【证监会令 第152号】&#xff0c;以下简称《管理办法》&#xff09;&#xff0c;共7章64条。《管理办…

Hyperchain 超块链创始人史兴国对谈杨民道:新公链赛道烽烟再起,move语言能开启下一轮牛市吗?

10月10日晚&#xff0c;观火琅琊榜第五季第十期播出&#xff0c;阁主史兴国先生&#xff08;中国计算机学会区块链专委会委员、Hyperchain 超块链创始人、国家科技进步奖获得者、前中科院软件所互联网实验室总工程师&#xff09;在本期的访谈嘉宾是杨民道&#xff08;dForce创始…

重磅 | 2022年第三季度Web3.0行业安全报告

2022年第三季度&#xff0c;黑客从Web3.0领域中盗取了总价值约5亿美元的资产&#xff0c;这个数字相比上季度减少了32.32%&#xff0c;同时今年的损失总额增长至28.8亿美元。 第三季度共发生了98起退出骗局&#xff0c;导致了5619万美元的资产损失&#xff0c;23起闪电贷攻击则…

! LaTeX Error: File xxx.sty not found-统一解决办法

在使用一些模板时常见这个错&#xff0c;其实就是缺宏包&#xff01;解决方案如下&#xff01; 第一步&#xff1a;在网站 https://ctan.org/pkg 找到你缺失的宏包&#xff0c;下载zip文件。 第二步&#xff1a;将解压后的文件夹复制到安装路径下&#xff1a; 如&#xff…

SOLIDWORKS 2023新功能揭秘!装配体升级 阵列实例、配合错误修复、零件替换同步更新

SOLIDWORKS 2023全新面世&#xff0c;今天众联亿诚为大家带来SOLIDWORKS 2023装配体的新功能揭秘&#xff0c;SOLIDWORKS 2023对装配体进行功能增强&#xff0c;并且继续加强性能&#xff0c;让我们深入研究这些令人振奋的新功能吧&#xff01;在装配体中阵列是一种常见的、节省…

搭建 Go 语言的开发环境(文末附视频讲解)

从本小节开始&#xff0c;我们就要正式动手实践了。 类比现实生活&#xff0c;我们若要钉钉子&#xff0c;就需要准备锤子&#xff1b;想要烧菜&#xff0c;就需要准备灶具和食材…… 类似地&#xff0c;若要在电脑上编写 Go 语言程序&#xff0c;便要先配置开发环境。 下载和…

大数据处理系统都有哪些?(批处理系统与迭代计算系统)

我们在前面的文章中给大家介绍了数据查询分析计算系统&#xff0c;数据查询分析计算系统是一个比较常见的系统&#xff0c;其实除了这一个数据查询分析计算系统还有很多系。我们在这篇文章中给大家介绍一下批处理系统和迭代计算系统&#xff0c;希望这篇文章能够给大家带来帮助…