Spring Boot整合Apache Shiro实现认证和授权功能

168 阅读9分钟

第一步、创建项目 在IntelliJ IDEA创建一个springboot项目springboot-shiro

第二步、添加依赖 修改pom.xml,添加相关的maven依赖

4.0.0

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.5.9</version>
	<relativePath />
</parent>

<groupId>cn.edu.sgu.www</groupId>
<artifactId>springboot-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro</name>
<description>Spring Boot整合Apache Shiro案例项目</description>

<properties>
	<java.version>1.8</java.version>
</properties>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>

	<!--lombok-->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.22</version>
	</dependency>

	<!--mysql-->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>8.0.28</version>
	</dependency>

	<!--mybatis-->
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>2.2.2</version>
	</dependency>

	<!--druid-->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid</artifactId>
		<version>1.1.21</version>
	</dependency>

	<!--fastjson-->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>2.0.8</version>
	</dependency>

	<!-- shiro -->
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-spring</artifactId>
		<version>1.3.2</version>
	</dependency>
</dependencies>

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

第三步、修改配置 将配置文件application.properties重命名为application.yml,修改配置文件的内容。

设置启动端口号

server: port: 8080

mybatis的mapper.xml文件的位置

mybatis: mapper-locations: classpath:mapper/*Mapper.xml

spring:

配置数据源

datasource: username: root password: root url: jdbc:mysql://localhost:3306/shiro driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource

只返回不为null的数据

jackson: default-property-inclusion: non_null

第四步、准备数据 1、创建数据库 通过navicat新建数据库shiro,没有用过navicat的童鞋可以参考博主的文章 推荐一款非常简单实用的数据库连接工具Navicat Premium blog.csdn.net/heyl163_/ar…

2、执行SQL脚本 在数据库shiro下执行以下SQL脚本

SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;


-- Table structure for user


DROP TABLE IF EXISTS user; CREATE TABLE user ( id varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户ID', username varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', password varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', phone varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号', gender tinyint(4) UNSIGNED NOT NULL COMMENT '性别(1-男;2-女)', lock_state tinyint(4) UNSIGNED NOT NULL COMMENT '锁定状态(0-正常;1-锁定)', last_login_time datetime NULL DEFAULT NULL COMMENT '上一次登录时间(未登录默认为null)', PRIMARY KEY (id) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;


-- Records of user


INSERT INTO user VALUES ('20241215', 'mumu', 'mhxy1218', '18888888888', 2, 0, NULL);

SET FOREIGN_KEY_CHECKS = 1;

第五步、创建公共类 ResponseCode.java package cn.edu.sgu.www.shiro.restful;

/**

  • 响应状态码

  • @author heyunlin

  • @version 1.0 / public enum ResponseCode { /*

    • 请求成功 / OK(200), /*
    • 失败的请求 / BAD_REQUEST(400), /*
    • 未授权 / UNAUTHORIZED(401), /*
    • 禁止访问 / FORBIDDEN(403), /*
    • 找不到(该状态不可用) / NOT_FOUND(404), /*
    • 不可访问 / NOT_ACCEPTABLE(406), /*
    • 冲突 / CONFLICT(409), /*
    • 服务器发生异常 */ ERROR(500);

    private final Integer value;

    ResponseCode(Integer value) { this.value = value; }

    public Integer getValue() { return value; }

}

JsonResult.java package cn.edu.sgu.www.shiro.restful;

import lombok.Data;

/**

  • 响应实体类

  • @author heyunlin

  • @version 1.0 */ @Data public class JsonResult {

    /**

    • 响应数据 */ private T data;

    /**

    • 响应状态码 */ private Integer code;

    /**

    • 响应提示信息 */ private String message;

    public static JsonResult success() { return success(null); }

    public static JsonResult success(String message) { return success(message, null); }

    public static JsonResult success(String message, T data) { JsonResult jsonResult = new JsonResult<>();

     jsonResult.setCode(ResponseCode.OK.getValue());
     jsonResult.setMessage(message);
     jsonResult.setData(data);
    
     return jsonResult;
    

    }

    public static JsonResult error(String message) { return error(ResponseCode.ERROR, message); }

    public static JsonResult error(ResponseCode responseCode, Throwable e) { return error(responseCode, e.getMessage() != null ? e.getMessage() : "系统发生异常,请联系管理员!"); }

    public static JsonResult error(ResponseCode responseCode, String message) { JsonResult jsonResult = new JsonResult<>();

     jsonResult.setCode(responseCode.getValue());
     jsonResult.setMessage(message);
    
     return jsonResult;
    

    }

}

GlobalException.java package cn.edu.sgu.www.shiro.exception;

import cn.edu.sgu.www.shiro.restful.ResponseCode; import lombok.Data; import lombok.EqualsAndHashCode;

/**

  • 自定义全局异常

  • @author heyunlin

  • @version 1.0 */ @EqualsAndHashCode(callSuper = true) @Data public class GlobalException extends RuntimeException {

    /**

    • 响应状态码 */ private ResponseCode responseCode;

    public GlobalException(ResponseCode responseCode, String message) { super(message);

     setResponseCode(responseCode);
    

    }

}

GlobalExceptionHandler.java package cn.edu.sgu.www.shiro.restful.handler;

import cn.edu.sgu.www.shiro.exception.GlobalException; import cn.edu.sgu.www.shiro.restful.JsonResult; import cn.edu.sgu.www.shiro.restful.ResponseCode; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; 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.HttpServletResponse; import java.util.Objects;

/**

  • 全局异常处理类

  • @author heyunlin

  • @version 1.0 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler {

    /**

    • 处理GlobalException */ @ExceptionHandler public JsonResult handleGlobalException(GlobalException e, HttpServletResponse response) { printMessage(e);

      response.setStatus(e.getResponseCode().getValue());

      return JsonResult.error(e.getResponseCode(), e); }

    /**

    • 处理AuthenticationException */ @ExceptionHandler @ResponseStatus(HttpStatus.FORBIDDEN) public JsonResult handleAuthenticationException(AuthenticationException e) { printMessage(e);

      return JsonResult.error(ResponseCode.FORBIDDEN, e); }

    /**

    • 处理BindException */ @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public JsonResult handleBindException(BindException e) { printMessage(e);

      BindingResult bindingResult = e.getBindingResult(); FieldError fieldError = bindingResult.getFieldError(); String defaultMessage = Objects.requireNonNull(fieldError).getDefaultMessage();

      return JsonResult.error(ResponseCode.BAD_REQUEST, defaultMessage); }

    /**

    • 处理MethodArgumentNotValidException */ @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public JsonResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { printMessage(e);

      BindingResult bindingResult = e.getBindingResult(); FieldError fieldError = bindingResult.getFieldError(); String defaultMessage = Objects.requireNonNull(fieldError).getDefaultMessage();

      return JsonResult.error(ResponseCode.BAD_REQUEST, defaultMessage); }

    /**

    • 处理NullPointerException */ @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public JsonResult handleNullPointerException(NullPointerException e) { printMessage(e);

      return JsonResult.error(ResponseCode.ERROR, "不规范的代码/请求导致了空指针异常NPE"); }

    /**

    • 处理Exception */ @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public JsonResult handleException(Exception e) { printMessage(e);

      return JsonResult.error(ResponseCode.ERROR, e); }

    /**

    • 打印异常信息

    • @param e Exception */ private void printMessage(Exception e) { String message = e.getMessage();

      log.error(message);

      e.printStackTrace(); }

}

第六步、创建user表相关的类 User.java package cn.edu.sgu.www.shiro.entity;

import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data;

import java.io.Serializable; import java.time.LocalDateTime;

/**

  • 用户

  • @author heyunlin

  • @version 1.0 */ @Data public class User implements Serializable { private static final long serialVersionUID = 18L;

    /**

    • 用户ID */ private String id;

    /**

    • 用户名 */ private String username;

    /**

    • 密码 */ private String password;

    /**

    • 手机号 */ private String phone;

    /**

    • 性别
    • 1-男;2-女 */ private Integer gender;

    /**

    • 锁定状态
    • 0-正常;1-锁定 */ private Integer lockState;

    /**

    • 最后一次登录时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime lastLoginTime; }

UserMapper.java package cn.edu.sgu.www.shiro.mapper;

import org.springframework.stereotype.Repository;

/**

  • @author heyunlin
  • @version 1.0 */ @Repository public interface UserMapper {

} UserService.java package cn.edu.sgu.www.shiro.service;

/**

  • @author heyunlin
  • @version 1.0 */ public interface UserService {

} UserController.java package cn.edu.sgu.www.shiro.controller;

import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

/**

  • @author heyunlin
  • @version 1.0 */ @RestController @RequestMapping(path = "/user", produces = "application/json;charset=utf-8") public class UserController {

} UserServiceImpl.java package cn.edu.sgu.www.shiro.service.impl;

import cn.edu.sgu.www.shiro.service.UserService; import org.springframework.stereotype.Service;

/**

  • @author heyunlin
  • @version 1.0 */ @Service public class UserServiceImpl implements UserService {

} 配置mapper包扫描路径

在springboot的启动类或者任意一个配置类上使用注解@MapperScan注解配置mybatis的mapper包扫描路径

@MapperScan("cn.edu.sgu.www.shiro.mapper") 在项目根目录下创建一个config包,在config包下面创建一个MybatisConfig类。

package cn.edu.sgu.www.shiro.config;

import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration;

/**

  • mybatis配置类
  • @author heyunlin
  • @version 1.0 */ @Configuration @MapperScan("cn.edu.sgu.www.shiro.mapper") public class MybatisConfig {

}

第七步、创建UsernameRealm 项目根包下创建realm包,在realm包下创建UsernameRealm.java,并继承AuthorizingRealm,重写AuthorizingRealm的两个认证和授权的抽象方法。

package cn.edu.sgu.www.shiro.shiro;

import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.stereotype.Component;

/**

  • @author heyunlin

  • @version 1.0 */ @Component public class UsernameRealm extends AuthorizingRealm {

    /**

    • 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { return null; }

    /**

    • 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; }

}

第八步、创建Shiro配置类 在项目根包下创建config包,在config包下创建ShiroConfig.java

package cn.edu.sgu.www.shiro.config;

import cn.edu.sgu.www.shiro.shiro.UsernameRealm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;

/**

  • shiro配置类

  • @author heyunlin

  • @version 1.0 */ @Configuration public class ShiroConfig {

    /**

    • 配置安全管理器

    • @param usernameRealm UserRealm

    • @return DefaultWebSecurityManager */ @Bean public DefaultWebSecurityManager securityManager(UsernameRealm usernameRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

      securityManager.setRealm(usernameRealm);

      return securityManager; }

    /**

    • 配置Shiro过滤器工厂

    • @param securityManager 安全管理器

    • @return ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

      // 注册安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager);

      /*

      • 设置登录页面的地址
      • 当用户访问认证资源的时候,如果用户没有登录,那么就会跳转到该属性指定的页面 */ shiroFilterFactoryBean.setLoginUrl("/login.html");

      return shiroFilterFactoryBean; }

}

第九步、实现登录认证功能 UserLoginDTO.java 创建一个DTO类来接收用户提交的用户名和密码。

package cn.edu.sgu.www.shiro.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank; import java.io.Serializable;

/**

  • @author heyunlin

  • @version 1.0 */ @Data public class UserLoginDTO implements Serializable { private static final long serialVersionUID = 18L;

    /**

    • 用户名 */ @NotBlank(message = "用户名不允许为空") private String username;

    /**

    • 密码 */ @NotBlank(message = "密码不允许为空") private String password; }

UserService.java package cn.edu.sgu.www.shiro.service;

import cn.edu.sgu.www.shiro.dto.UserLoginDTO;

/**

  • @author heyunlin

  • @version 1.0 */ public interface UserService {

    /**

    • 登录认证
    • @param loginDTO 登录信息 */ void login(UserLoginDTO loginDTO); }

UserMapper.java package cn.edu.sgu.www.shiro.mapper;

import cn.edu.sgu.www.shiro.entity.User; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository;

/**

  • @author heyunlin

  • @version 1.0 */ @Repository public interface UserMapper {

    /**

    • 根据用户名查询用户信息
    • @param username 用户名
    • @return User 查询到的用户信息 */ @Select("select * from user where username = #{username}") @Results({ @Result(column = "id", property = "id"), @Result(column = "username", property = "username"), @Result(column = "password", property = "password"), @Result(column = "phone", property = "phone"), @Result(column = "gender", property = "gender"), @Result(column = "lock_state", property = "lockState"), @Result(column = "last_login_time", property = "lastLoginTime") }) User selectByUsername(String username); }

UserController.java package cn.edu.sgu.www.shiro.controller;

import cn.edu.sgu.www.shiro.dto.UserLoginDTO; import cn.edu.sgu.www.shiro.restful.JsonResult; import cn.edu.sgu.www.shiro.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController;

/**

  • @author heyunlin

  • @version 1.0 */ @RestController @RequestMapping(path = "/user", produces = "application/json;charset=utf-8") public class UserController {

    private final UserService userService;

    @Autowired public UserController(UserService userService) { this.userService = userService; }

    /**

    • 登录认证

    • @param loginDTO 登录信息

    • @return JsonResult */ @RequestMapping(path = "/login", method = RequestMethod.POST) public JsonResult login(@Validated UserLoginDTO loginDTO) { userService.login(loginDTO);

      return JsonResult.success("登录成功"); }

}

UserServiceImpl.java package cn.edu.sgu.www.shiro.service.impl;

import cn.edu.sgu.www.shiro.dto.UserLoginDTO; import cn.edu.sgu.www.shiro.exception.GlobalException; import cn.edu.sgu.www.shiro.restful.ResponseCode; import cn.edu.sgu.www.shiro.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Service;

/**

  • @author heyunlin

  • @version 1.0 */ @Service public class UserServiceImpl implements UserService {

    @Override public void login(UserLoginDTO loginDTO) { // shiro登录认证 UsernamePasswordToken token = new UsernamePasswordToken(loginDTO.getUsername(), loginDTO.getPassword()); Subject subject = SecurityUtils.getSubject();

     try {
         subject.login(token);
     } catch (IncorrectCredentialsException e) {
         throw new GlobalException(ResponseCode.FORBIDDEN, "登录失败,用户名或密码错误!");
     }
    
     // 设置session失效时间:永不超时
     subject.getSession().setTimeout(-1001);
    

    }

}

UsernameRealm.java package cn.edu.sgu.www.shiro.shiro;

import cn.edu.sgu.www.shiro.entity.User; import cn.edu.sgu.www.shiro.mapper.UserMapper; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;

/**

  • @author heyunlin

  • @version 1.0 */ @Component public class UsernameRealm extends AuthorizingRealm {

    private final UserMapper userMapper;

    @Autowired public UsernameRealm(UserMapper userMapper) { this.userMapper = userMapper; }

    /**

    • 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

      // 得到用户名 String username = token.getUsername(); // 根据用户名查询用户信息 User user = userMapper.selectByUsername(username);

      if (user != null) { if (user.getLockState() == 1) { throw new AuthenticationException("账号已被锁定,禁止登录!"); }

       return new SimpleAuthenticationInfo(user, user.getPassword(), username);
      

      } else { throw new AuthenticationException("登录失败,用户名不存在!"); } }

    /**

    • 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; }

}

第十步:测试登录认证功能 SpringbootShiro.java 在任意配置类上使用@MapperScan注解,指定mybatis的mapper接口的包。

@MapperScan(basePackages = "cn.edu.sgu.www.shiro.mapper") 可以直接在启动类上使用

package cn.edu.sgu.www.shiro;

import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;

/**

  • @author heyunlin

  • @version 1.0 */ @MapperScan(basePackages = "cn.edu.sgu.www.shiro.mapper") @SpringBootApplication public class SpringbootShiro {

    public static void main(String[] args) { SpringApplication.run(SpringbootShiro.class, args); }

}

ShiroConfig.java 在shiroFilter方法的倒数第二行添加以下代码,配置系统首页需要登录认证才能访问。

// 定义资源访问规则 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

/*

  • 过滤器说明
  • anon:不需要认证就可以访问的资源
  • authc:需要登录认证才能访问的资源
  • perms:需要指定权限才能访问的资源 */ filterChainDefinitionMap.put("/", "authc"); filterChainDefinitionMap.put("/index.html", "authc");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 完整的ShiroConfig.java代码

package cn.edu.sgu.www.shiro.config;

import cn.edu.sgu.www.shiro.shiro.UsernameRealm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap; import java.util.Map;

/**

  • shiro配置类

  • @author heyunlin

  • @version 1.0 */ @Configuration public class ShiroConfig {

    /**

    • 配置安全管理器

    • @param usernameRealm UserRealm

    • @return DefaultWebSecurityManager */ @Bean public DefaultWebSecurityManager securityManager(UsernameRealm usernameRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

      securityManager.setRealm(usernameRealm);

      return securityManager; }

    /**

    • 配置Shiro过滤器工厂

    • @param securityManager 安全管理器

    • @return ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

      // 注册安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager);

      /*

      • 设置登录页面的地址
      • 当用户访问认证资源的时候,如果用户没有登录,那么就会跳转到该属性指定的页面 */ shiroFilterFactoryBean.setLoginUrl("/login.html");

      // 定义资源访问规则 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

      /*

      • 过滤器说明
      • anon:不需要认证就可以访问的资源
      • authc:需要登录认证才能访问的资源
      • perms:需要指定权限才能访问的资源 */ filterChainDefinitionMap.put("/", "authc"); filterChainDefinitionMap.put("/index.html", "authc");

      shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

      return shiroFilterFactoryBean; }

}

login.html 在resources目录下创建static目录,在static目录下创建一个页面文件login.html

登录页面
<body>
    <form>
        <table>
            <tr>
                <td>用户名</td>
                <td><input type="text" id="username" /></td>
            </tr>
    
            <tr>
                <td>密码</td>
                <td><input type="password" id="password" /></td>
            </tr>
    
            <tr>
                <td>
                    <button type="button" id="login">登录</button>
                </td>
                <td>
                    <button type="reset">重置</button>
                </td>
            </tr>
        </table>
    </form>
    
    <script src="/js/jquery.min.js"></script>
    <script src="/js/login.js"></script>
</body>

index.html 在static目录下创建一个页面文件index.html,表示系统首页。

系统首页
<body>
    <h1>Welcome to the system.</h1>
</body>
login.js 在static目录下创建一个js目录,然后在js目录下创建login.js

把jquery.min.js复制到js目录下

$(document).ready(function () {

$("#login").click(function () {
    let username = $("#username").val();
    let password = $("#password").val();

    $.post("/user/login", {
        username: username,
        password: password
    }, function (resp) {
       if (resp.code === 200) {
           location.href = "/html/home.html";
       } else {
           alert(resp.message);
       }
    });
});

});

最后启动项目,在浏览器地址栏输入以下任意一个网址,访问项目的首页index.html

http://localhost:8080 http://localhost:8080/index.html

如果跳转到登录页面,则说明配置的过滤器生效了~

输入用户名、密码mumu/mhxy1218,点击登录按钮,如果成功跳转到系统首页,测试就完成了~

第十一步:实现授权功能 UsernameRealm.java 完成doGetAuthorizationInfo()方法的具体实现

package cn.edu.sgu.www.shiro.shiro;

import cn.edu.sgu.www.shiro.entity.User; import cn.edu.sgu.www.shiro.mapper.UserMapper; import org.apache.shiro.authc.*; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;

import java.util.HashSet; import java.util.Set;

/**

  • @author heyunlin

  • @version 1.0 */ @Component public class UsernameRealm extends AuthorizingRealm {

    private final UserMapper userMapper;

    @Autowired public UsernameRealm(UserMapper userMapper) { this.userMapper = userMapper; }

    /**

    • 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

      // 得到用户名 String username = token.getUsername(); // 根据用户名查询用户信息 User user = userMapper.selectByUsername(username);

      if (user != null) { if (user.getLockState() == 1) { throw new AuthenticationException("账号已被锁定,禁止登录!"); }

       return new SimpleAuthenticationInfo(user, user.getPassword(), username);
      

      } else { throw new AuthenticationException("登录失败,用户名不存在!"); } }

    /**

    • 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Set permissions = new HashSet<>();

      permissions.add("user:delete"); permissions.add("user:update");

      authorizationInfo.setStringPermissions(permissions);

      return authorizationInfo; }

}

UserController.java 在UserController中添加两个接口/user/delete和/user/update

package cn.edu.sgu.www.shiro.controller;

import cn.edu.sgu.www.shiro.dto.UserLoginDTO; import cn.edu.sgu.www.shiro.restful.JsonResult; import cn.edu.sgu.www.shiro.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController;

/**

  • @author heyunlin

  • @version 1.0 */ @RestController @RequestMapping(path = "/user", produces = "application/json;charset=utf-8") public class UserController {

    private final UserService userService;

    @Autowired public UserController(UserService userService) { this.userService = userService; }

    /**

    • 登录认证

    • @param loginDTO 登录信息

    • @return JsonResult */ @RequestMapping(path = "/login", method = RequestMethod.POST) public JsonResult login(@Validated UserLoginDTO loginDTO) { userService.login(loginDTO);

      return JsonResult.success("登录成功"); }

    @RequestMapping(path = "/delete", method = RequestMethod.POST) public JsonResult delete() { return JsonResult.success("删除成功"); }

    @RequestMapping(path = "/update", method = RequestMethod.POST) public JsonResult update() { return JsonResult.success("修改成功"); }

}

ShiroConfig.java 指定两个接口需要指定的权限才能访问。

// 需要指定权限才能访问的资源 filterChainDefinitionMap.put("/user/delete", "perms[user:delete]"); filterChainDefinitionMap.put("/user/update", "perms[user:update]"); 完整的ShiroConfig.java代码

package cn.edu.sgu.www.shiro.config;

import cn.edu.sgu.www.shiro.shiro.UsernameRealm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap; import java.util.Map;

/**

  • shiro配置类

  • @author heyunlin

  • @version 1.0 */ @Configuration public class ShiroConfig {

    /**

    • 配置安全管理器

    • @param usernameRealm UserRealm

    • @return DefaultWebSecurityManager */ @Bean public DefaultWebSecurityManager securityManager(UsernameRealm usernameRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

      securityManager.setRealm(usernameRealm);

      return securityManager; }

    /**

    • 配置Shiro过滤器工厂

    • @param securityManager 安全管理器

    • @return ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

      // 注册安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager);

      /*

      • 设置登录页面的地址
      • 当用户访问认证资源的时候,如果用户没有登录,那么就会跳转到该属性指定的页面 */ shiroFilterFactoryBean.setLoginUrl("/login.html");

      // 定义资源访问规则 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

      /*

      • 过滤器说明
      • anon:不需要认证就可以访问的资源
      • authc:需要登录认证才能访问的资源
      • perms:需要指定权限才能访问的资源 */ // 需要登录认证才能访问的资源 filterChainDefinitionMap.put("/", "authc"); filterChainDefinitionMap.put("/index.html", "authc");

      // 需要指定权限才能访问的资源 filterChainDefinitionMap.put("/user/delete", "perms[user:delete]"); filterChainDefinitionMap.put("/user/update", "perms[user:update]");

      shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

      return shiroFilterFactoryBean; }

}

index.html 引入jquery,新增两个按钮,绑定点击事件,直接给对应的接口发送Ajax post请求。

系统首页
<body>
    <h1>Welcome to the system.</h1>

    <button type="button" id="delete">删除</button> | <button type="button" id="update">修改</button>

    <script>
        $(function () {
            $("#delete").click(function () {
                $.post("/user/delete", function (resp) {
                    alert(resp.message);
                });
            });

            $("#update").click(function () {
                $.post("/user/update", function (resp) {
                    alert(resp.message);
                });
            });

        });
    </script>
</body>

把自定义过滤器加入到shiro的过滤器链中,处理删除和修改两个接口。

map.put("/user/delete", "authorization"); map.put("/user/update", "authorization");

package cn.edu.sgu.www.shiro.config;

import cn.edu.sgu.www.shiro.filter.AuthorizationFilter; import cn.edu.sgu.www.shiro.realm.UserRealm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map;

/**

  • shiro配置类 */ @Configuration public class ShiroConfig {

    /**

    • 配置安全管理器

    • @param userRealm UserRealm

    • @return DefaultWebSecurityManager */ @Bean public DefaultWebSecurityManager securityManager(UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

      securityManager.setRealm(userRealm);

      return securityManager; }

    /**

    • 配置Shiro过滤器工厂

    • @param securityManager 安全管理器

    • @return ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

      // 注册安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager);

      /*

      • 设置登录页面的地址
      • 当用户访问认证资源的时候,如果用户没有登录,那么就会跳转到指定的页面 */ shiroFilterFactoryBean.setLoginUrl("/login.html");

      // 定义资源访问规则 Map<String, String> map = new LinkedHashMap<>();

      /*

      • 过滤器说明
      • anon:不需要认证就可以访问的资源
      • authc:需要登录认证才能访问的资源 */ map.put("/html/home.html", "authc");

      // 不需要认证就能访问 map.put("/login.html", "anon"); map.put("/user/login", "anon");

      // 设置自定义过滤器 map.put("/user/delete", "authorization"); map.put("/user/update", "authorization");

      shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

      Map<String, Filter> filters = shiroFilterFactoryBean.getFilters(); filters.put("authorization", new AuthorizationFilter()); shiroFilterFactoryBean.setFilters(filters);

      shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

      return shiroFilterFactoryBean; }

}

第十二步、测试授权功能 1、正常访问 启动/重启项目,登录之后点击两个按钮,因为在UsernameRealm中分配了对应的权限,所以能够访问成功,并且得到了接口返回的提示信息。

2、访问失败 注释掉修改接口的权限,然后重启项目。

登录之后点击修改按钮,没有任何反应,打开浏览器控制台,发现接口报错了。

至此,授权功能的测试也完成了。

————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                    

原文链接:blog.csdn.net/heyl163_/ar…