第一步、创建项目 在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 版权协议,转载请附上原文出处链接和本声明。