参考源码
1,添加引用
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.springboot</groupId>
<artifactId>Spring-Boot-Shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<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-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
引用shiro的jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2,application.yml
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://192.168.16.128:3306/ds?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&serverTimezone=UTC
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
3,ShiroConfig
添加shiro配置。
ShiroRealm 实现用户的登录,用户权限查询。 有2个待实现的方法:
AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) :用户登录
AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) : 查询用户角色,权限
ShiroFilterFactoryBean 设置权限集合,登录路径,登录成功路径等。
anon 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤
authc 需登录,如果没有登录会跳到相应的登录页面登录
perms 权限授权拦截器,验证用户是否拥有所有权限;示例/user/**=perms["user:create"]
roles 角色授权拦截器,验证用户是否拥有所有角色;示例/admin/**=roles[admin]
ssl SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口443;其他和port拦截器一样;
user 登录/记住我登录的都可;示例/**=user
SecurityManager 安全管理,是上面的配置生效。
@Configuration
public class ShiroConfig {
/**
* 配置权限
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/", "anon");
//如果要使用 Remember : 需把 authc 改成 user
//user指的是用户认证通过或者配置了Remember Me记住用户登录状态后可访问
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 使配置生效
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* 配置授权,登录的Realm
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
}
4,ShiroRealm
ShiroRealm 这节只实现 用户登录,用户权限查询下一节实现
public class ShiroRealm extends AuthorizingRealm {
@Resource
private UserMapper userMapper;
/**
* 获取用户角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//下一章讲解
return null ;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
System.out.println("用户" + userName + "认证-----ShiroRealm.doGetAuthenticationInfo");
User user = userMapper.getUserByName(userName);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
if (user.getEnabledStatus() == 1) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
}
user 用户表结构:
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
`real_name` varchar(50) NOT NULL DEFAULT '' COMMENT '姓名',
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '账号',
`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
`mobile` varchar(11) NOT NULL COMMENT '手机号',
`age` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
`enabled_status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '启用状态:0 启用,1 停用',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`active` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '删除标识:0删除,1存活',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';
用户java对象User:
@Data
@Alias("User")
public class User implements Serializable {
/**
* 唯一标识
*/
private Long id;
/**
* 姓名
*/
private String realName;
/**
* 账号
*/
private String name;
/**
* 密码
*/
private String password;
/**
* 年龄
*/
private Integer age;
/**
* 启用状态:0 启用,1 停用
*/
private Integer enabledStatus;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 删除标识:0删除,1存活
*/
private Integer active;
}
ShiroRealm 中为了方便直接使用Mapper查询User,你也可以把UserMapper包装成UserService。
/**
* @author qizenan
* @date 2020/11/11 11:42
*/
@Mapper
public interface UserMapper {
/**
* 创建用户信息
*
* @param user 实体
* @return int
*/
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert(" INSERT INTO users ( real_name ,name ,password ,age ,enabled_status ,created_at ,active) " +
" VALUES ( #{realName} ,#{name} ,#{password} ,#{age} ,#{enabledStatus} ,NOW() ,1) ")
int createUser(User user);
/**
* 更新用户信息
*
* @param user 实体
* @return int
*/
@Update(" UPDATE users SET real_name=#{realName},name=#{name},password=#{password},age=#{age},enabled_status=#{enabledStatus} WHERE id=#{id} ")
int updateUser(User user);
/**
* 删除用户信息
*
* @param id 唯一标识id
* @return int
*/
@Update("UPDATE users SET active=0 WHERE id=#{id}")
int deleteUser(@Param("id") long id);
/**
* 按 账号 查找用户信息
*
* @param name 账号
* @return User
*/
@Select("SELECT id,real_name,name,password,age,enabled_status,created_at,active FROM users WHERE name=#{name} LIMIT 1 ")
User getUserByName(@Param("name") String name);
}
5,controller
@Controller
public class LoginController {
/**
* 登录页面
*/
@GetMapping("/login")
public String login() {
return "login";
}
/**
* 登录
*
* @param username 用户账号
* @param password 用户密码
* @param rememberMe 是否记住登录:true 是,false 否
*/
@PostMapping("/login")
@ResponseBody
public ResponseBo login(String username, String password) {
password = MD5Utils.encrypt(username, password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return ResponseBo.ok();
} catch (UnknownAccountException e) {
return ResponseBo.error(e.getMessage());
} catch (IncorrectCredentialsException e) {
return ResponseBo.error(e.getMessage());
} catch (LockedAccountException e) {
return ResponseBo.error(e.getMessage());
} catch (AuthenticationException e) {
return ResponseBo.error("认证失败!");
}
}
/**
* 默认 / 重定向到index页面
*/
@RequestMapping("/")
public String redirectIndex() {
return "redirect:/index";
}
/**
* index 页面
*/
@RequestMapping("/index")
public String index(Model model) {
User user = (User) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user", user);
return "index";
}
/**
* 错误页面
*/
@GetMapping("/403")
public String forbid() {
return "403";
}
}
@Controller
@RequestMapping("/user")
public class UserController {
@RequiresPermissions("user:list")
@RequestMapping("list")
public String userList(Model model) {
model.addAttribute("value", "获取用户信息");
return "user";
}
@RequiresPermissions("user:add")
@RequestMapping("add")
public String userAdd(Model model) {
model.addAttribute("value", "新增用户");
return "user";
}
@RequiresPermissions("user:delete")
@RequestMapping("delete")
public String userDelete(Model model) {
model.addAttribute("value", "删除用户");
return "user";
}
}
6,页面
登录页面 login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" th:href="@{/css/login.css}" type="text/css">
<script th:src="@{/js/jquery-1.11.1.min.js}"></script>
</head>
<body>
<div class="login-page">
<div class="form">
<input type="text" placeholder="用户名" name="username" required="required"/>
<input type="password" placeholder="密码" name="password" required="required"/>
<button onclick="login()">登录</button>
</div>
</div>
</body>
<script th:inline="javascript">
var ctx = [[@{
/}]];
function login() {
var username = $("input[name='username']").val();
var password = $("input[name='password']").val();
$.ajax({
type: "post",
url: ctx + "login",
data: {
"username": username,"password": password},
dataType: "json",
success: function (r) {
if (r.code == 0) {
location.href = ctx + 'index';
} else {
alert(r.msg);
}
}
});
}
</script>
</html>
index页面
<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" >
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<style>
div {
border: 1px dashed #ddd;
padding: 10px;
margin: 10px 10px 10px 0px;
}
</style>
<body>
<p>你好![[${user.realName}]]</p>
<a th:href="@{/logout}">注销</a>
</body>
</html>
错误页面403
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>暂无权限</title>
</head>
<body>
<p>您没有权限访问该资源!!</p>
<a th:href="@{/index}">返回</a>
</body>
</html>
7,测试
访问 http://127.0.0.1:8080/ ,因为初次访问,所以需要登录
登录成功后