一、基础环境搭建
1、新建SpringBoot项目,并导入以下依赖
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<!-- thymeleaf依赖 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
</dependencies>
2、在templates目录下新建index.html,同时新建一个controller包,创建一个类MyController.java,如下:
index.html
MyController.java
3、启动该项目,在浏览器中输入控制器中的请求路径,可以看到访问的首页,环境搭建成功。
二、整合Shiro
1、导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
2、新建config包,在下面创建shiro配置类,如下:
@Configuration
public class ShiroConfig {
//1、ShiroFilteFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//配置安全管理器
factoryBean.setSecurityManager(securityManager);
return factoryBean;
}
//2、DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联自定义的realm
securityManager.setRealm(userRealm);
return securityManager;
}
//3、Realm
@Bean
public UserRealm getUserRealm() {
return new UserRealm();
}
/**
* 自定义realm
*/
private class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权操作");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证操作");
return null;
}
}
}
3、新建页面
add.html
和update.html
,同时修改index.html
,如下:
add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<h1>添加用户</h1>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改用户</title>
</head>
<body>
<h1>修改用户</h1>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<a th:href="@{/user/add.html}">add</a>
<a th:href="@{/user/update.html}">update</a>
</body>
</html>
4、在MyController.java中新添加两个方法,用于跳转到add.html和update.html页面,然后启动项目。
@RequestMapping("/user/add.html")
public String addUser(){
return "user/add";
}
@RequestMapping("/user/update.html")
public String updateUser(Model model){
return "user/update";
}
5、在浏览器中访问请求index.html页面,点击add和update,可以发现目前两个跳转的都可以跳转成功。
三、登录拦截
1、修改
ShiroConfig
中的getShiroFilterFactoryBean
方法
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//配置安全管理器
factoryBean.setSecurityManager(securityManager);
/**
* anon:无需认证就可以访问
* authc:必须认证了才能访问
* user:必须拥有 记住我 才能用
* perms:拥有某个资源的权限才能访问
* role:拥有某个角色的权限才能反问
*/
//添加shiro的内置过滤器
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add.html", "anon");
filterMap.put("/user/update.html", "authc");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
2、在MyController.java中新添加一个方法,用于跳转到登录页面,当访问没有认证时跳转到该页面。
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<h1>用户登录</h1>
<form action="">
<p>账号:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit" value="登录"></p>
</form>
</body>
</html>
MyController
@RequestMapping("/user/add.html")
public String addUser(){
return "user/add";
}
@RequestMapping("/user/update.html")
public String updateUser(Model model){
return "user/update";
}
3、重启项目,点击add和update,可以发现,add可以访问,update不能访问,会跳转到登录页面,因为没有认证。
四、认证
1、在控制器中添加方法,用于登录
@RequestMapping("/user/login")
public String login(String username, String password, Model model) {
//获得当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户登录数据,生成令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);//如果没有异常登陆成功。跳转到首页
return "index";
}catch (UnknownAccountException e){
model.addAttribute("msg", "账号错误");
return "user/login";
} catch (IncorrectCredentialsException e){
model.addAttribute("msg", "密码错误");
return "user/login";
}
}
2、修改login.html,接收错误消息
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<h1>用户登录</h1>
<form th:action="@{/user/login}">
<p>账号:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit" value="登录"></p>
<p th:text="${msg}" style="color: red"></p>
</form>
</body>
</html>
3、修改自定义的
UserRealm
中重写的doGetAuthenticationInfo
方法
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证操作");
String username = "admin";
String password = "admin";
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//用户名认证
if (!username.equals(userToken.getUsername())){
return null;//这里自动会抛出UnknownAccountException异常
}
//密码认证(会自动帮我们认证密码)
return new SimpleAuthenticationInfo("", password, "");
}
4、重启项目后点击update,进行登录操作输入与我们设置的数据不一致,会提示登录失败。随后,可以在idea的控制台看到自定义的
UserRealm
中重写的doGetAuthenticationInfo
方法输出的日志。
注意:
这说明一个问题,当登录时调用subject.login(token)
方法后,会去doGetAuthenticationInfo
方法中进行登录信息的认证,由于与设置的用户名和密码不匹配,所以会出现认证失败的问题。在实际开发中,会在doGetAuthenticationInfo
方法中进行登录用户的用户名和密码的验证
(可以从数据库中获取数据进行对比).
五、整合MyBatis
项目结构如下
sql脚本如下:
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50730
Source Host : localhost:3306
Source Schema : testdb
Target Server Type : MySQL
Target Server Version : 50730
File Encoding : 65001
Date: 20/09/2020 19:35:57
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`prams` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', 'root', 'user:add');
INSERT INTO `user` VALUES (2, 'test', 'test', 'user:add');
SET FOREIGN_KEY_CHECKS = 1;
1、添加依赖
<!-- 数据库连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
2、application.yml配置
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/testdb?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
dbcp2:
initial-size: 5
minIdle: 5
maxActive: 20
maxWait: 60000
mybatis:
# 配置mapper.xml文件的位置
mapper-locations: classpath:mapper/*.xml
3、创建实体类User
package com.xukun.shirodemo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
private String prams;
}
4、创建UserMapper.java接口
package com.xukun.shirodemo.mapper;
import com.xukun.shirodemo.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper {
User queryUserByName(String name);
}
5、创建UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xukun.shirodemo.mapper.UserMapper">
<select id="queryUserByName" resultType="com.xukun.shirodemo.entity.User">
select * from user where name = #{name}
</select>
</mapper>
6、创建Service(这里偷个懒,没有创建接口)
package com.xukun.shirodemo.service;
import com.xukun.shirodemo.entity.User;
import com.xukun.shirodemo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
UserMapper userMapper;
/**
* 根据用户名获得用户信息
*
* @param name
* @return
*/
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
7、在启动类上面使用@MapperScan注解,配置Mapper接口的位置
package com.xukun.shirodemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.xukun.shirodemo.mapper")
public class ShirodemoApplication {
public static void main(String[] args) {
SpringApplication.run(ShirodemoApplication.class, args);
}
}
8、在ShiroConfig.java中注入UserService,实现从数据库中获取数据。
@Autowired
UserService userService;
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证操作");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//用户名认证
User user = userService.queryUserByName(userToken.getUsername());
if (user == null) {
return null;//这里自动会抛出UnknownAccountException异常
}
//密码认证(会自动帮我们认证密码)
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}
9、再次重启项目,进行访问,可以看到登录效果。
六、授权
1、首先在控制器中添加一个方法,当某个资源未授权时我们调用这个方法。
@RequestMapping("/unanuthorized")
@ResponseBody
public String unanuthorized(){
return "未经授权不得访问!";
}
2、修改ShiroConfig中getShiroFilterFactoryBean方法的内容
//添加shiro的内置过滤器
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add.html", "perms[user:add]");
filterMap.put("/user/*.html", "authc");
factoryBean.setFilterChainDefinitionMap(filterMap);
//如果没有认证,跳转到登录页面进行登录认证
factoryBean.setLoginUrl("/user/login.html");
//如果没有授权跳转的请求地址
factoryBean.setUnauthorizedUrl("/unanuthorized");
3、重启项目,当登录后,点击add,浏览器会出现“未经授权不得访问”。
4、进行授权,修改UserRealm中doGetAuthorizationInfo方法的内容
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权操作");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//用户在登陆的时候会认证,认证的时候会得到用户的权限
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
//设置当前登录用户的权限
simpleAuthorizationInfo.addStringPermission(user.getPrams());
return simpleAuthorizationInfo;
}
5、重启项目,当登录后,进行不同用户登录,观察现象。