SpringBoot+SpringJPA+Swagger+Shiro快速搭建前后端分离的权限管理系统

SpringBoot+SpringJPA+Swagger+Shiro 快速搭建前后端分离的权限管理系统 前前后端分离,一般都是通过 token\实现,本项目也是一样;用户登录时

本文包含相关资料包-----> 点击直达获取<-------

SpringBoot+SpringJPA+Swagger+Shiro 快速搭建前后端分离的权限管理系统

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

Let's do it!!

介绍:这次我们使用 Shiro 快速搭建前后端分离的权限管理系统 利用 JPA 帮我们管理数据库,Swagger Knife4j 帮我搭建 Web 测试环境;

后台基于 SpringBoot JPA Knife4j Shiro

前端基于 VUE ElementUI

注意:主要观察 token 的使用方法!

第一步:新建工程

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

html <!--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>

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

第三步:编写登陆入口

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

```java @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 返回给前端。

```java /* * @Author 大誌 * @Date 2019/3/30 22:18 * @Version 1.0 / @Service public class ShiroServiceImpl implements ShiroService {

@Autowired
private UserRepository userRepository;
@Autowired
private SysTokenRepository sysTokenRepository;

/**
 * 根据username查找用户
 *
 * @param username
 * @return User
 */
@Override
public 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<>();
    //生成一个token
    String token = TokenGenerator.generateValue();
    //当前时间
    Date now = new Date();
    //过期时间
    Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
    //判断是否生成过token
    SysToken tokenEntity = sysTokenRepository.findByUserId(userId);
    if (tokenEntity == null) {
        tokenEntity = new SysToken();
        tokenEntity.setUserId(userId);
        tokenEntity.setToken(token);
        tokenEntity.setUpdateTime(now);
        tokenEntity.setExpireTime(expireTime);
        //保存token
        sysTokenRepository.save(tokenEntity);
    } else {
        tokenEntity.setToken(token);
        tokenEntity.setUpdateTime(now);
        tokenEntity.setExpireTime(expireTime);
        //更新token
        sysTokenRepository.save(tokenEntity);
    }
    result.put("token", token);
    result.put("expire", EXPIRE);
    return result;
}

@Override
public void logout(String token) {
    SysToken byToken = findByToken(token);
    //生成一个token
    token = TokenGenerator.generateValue();
    //修改token
    SysToken tokenEntity = new SysToken();
    tokenEntity.setUserId(byToken.getUserId());
    tokenEntity.setToken(token);
    sysTokenRepository.save(tokenEntity);
}

@Override
public SysToken findByToken(String accessToken) {
    return sysTokenRepository.findByToken(accessToken);

}

@Override
public User findByUserId(Integer userId) {
    return userRepository.findByUserId(userId);
}

} ```

第五步:编写 ShiroConfig 类

```java /* * @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();
}

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

} ```

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

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

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

@Override
public Object getPrincipal() {
    return token;
}

@Override
public Object getCredentials() {
    return token;
}

} ```

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

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

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

第七步:编写自己的 Realm

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

```java /* * @Author 大誌 * @Date 2019/3/30 21:38 * @Version 1.0 / @Component public class AuthRealm extends AuthorizingRealm {

@Autowired
private 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,既前端传入的token
    String 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 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, this.getName());
    return info;
}

} ```

第八步:实现自定义 AuthenticatingFilter。

```java /* * 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
 */
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
    //获取请求token
    String token = TokenUtil.getRequestToken((HttpServletRequest) request);

    return new AuthToken(token);
}

/**
 * 步骤1.所有请求全部拒绝访问
 *
 * @param request
 * @param response
 * @param mappedValue
 * @return
 */
@Override
protected 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
 */
@Override
protected 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失效时候调用
 */
@Override
protected 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 方法,其余都拦截。

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

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

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

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

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

看看效果

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

DTO

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

实体类

```java @Getter @Setter @Entity public class User { @Id private 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 {

@Id
private 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 {

@Id
private Integer permissionId;
private String permissionName;
private String permission;

}

@Getter @Setter @Entity public class SysToken{

@Id
private 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

```sql / 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。

```java /* * @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 异常处理器,用于捕获无权限时候的异常。

```java @ControllerAdvice public class MyExceptionHandler {

@ExceptionHandler(value = AuthorizationException.class)
@ResponseBody
public 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()进行权限和角色拦截

```java

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

```

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

参考文献

  • 通用权限管理系统研究与应用(电子科技大学·杨莹莹)
  • 基于OAuth2.0协议的企业分布式授权系统设计与实现(华中科技大学·支猛)
  • 统一认证授权平台的构建(成都理工大学·李勇)
  • 基于SSH资源管理系统的设计及实现(西安电子科技大学·杨静涛)
  • 基于Java EE平台的个人健康信息管理系统设计与实现(南京邮电大学·贾顺贺)
  • 基于EPP的域名管理系统(山东大学·孟庆领)
  • 基于Dubbo框架的开发者中心的设计与实现(北京交通大学·涂鹏程)
  • 基于Java EE平台的个人健康信息管理系统设计与实现(南京邮电大学·贾顺贺)
  • 统一认证授权平台的构建(成都理工大学·李勇)
  • 基于Struts的通用权限管理系统的设计与实现(电子科技大学·万静)
  • 基于SSH资源管理系统的设计及实现(西安电子科技大学·杨静涛)
  • 统一认证授权平台的构建(成都理工大学·李勇)
  • 项目管理原型系统—权限管理子系统的设计与实现(电子科技大学·甘沅鑫)
  • 基于OAuth2.0协议的企业分布式授权系统设计与实现(华中科技大学·支猛)
  • 办公流程管理系统的设计与实现(北京邮电大学·王影)

本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:源码导航 ,原文地址:https://bishedaima.com/yuanma/35844.html

相关推荐

发表回复

登录后才能评论