1、pom.xml文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.1.RELEASEversion>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>demoname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.5.3version>
dependency>
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
application.yml:
spring:
profiles:
active: dev
application-dev.yml:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: root
mybatis:
#mapper.xml的具体位置
mapper-locations: classpath:mapping/*Mapper.xml
#定义此包下的实体类的别名
type-aliases-package: com.example.demo.entity
项目结构图:
在entity包中编写 User 和 Role实体类(IDEA安装lombok插件,具体自己百度):
package com.example.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
private List<Role> roleList = null;
}
package com.example.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
private Integer id;
private String rolename;
}
在mapper包下编写接口UserMapper:
package com.example.demo.mapper;
import com.example.demo.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper {
//根据id查找用户
User selectUserById(Integer id);
//根据username查找用户
User selectUserByName(String username);
}
在resources/mapping目录下新建UserMapping.xml:
<mapper namespace="com.example.demo.mapper.UserMapper">
<resultMap id="userMap" type="user">
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<collection property="roleList" ofType="role">
<result column="rid" property="id"/>
<result column="rolename" property="rolename"/>
collection>
resultMap>
<select id="selectUserById" parameterType="Integer" resultMap="userMap">
select u.*,r.id as rid,r.rolename
from user as u
left join user_role as ur on u.id = ur.user_id
left join role as r on r.id = ur.role_id
where u.id = #{id}
select>
<select id="selectUserByName" parameterType="String" resultMap="userMap">
select u.*, r.id as rid,r.rolename
from user as u
left join user_role as ur on u.id = ur.user_id
left join role as r on r.id = ur.role_id
where u.username = #{username}
select>
mapper>
在service包和service.impl包下新建UserService接口和对应实现类UserServiceImpl:
package com.example.demo.service;
import com.example.demo.entity.User;
public interface UserService {
//根据id查询用户
User selectUserById(Integer id);
//根据用户名查询用户
User selectUserByName(String username);
}
package com.example.demo.service.impl;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User selectUserById(Integer id) {
return userMapper.selectUserById(id);
}
@Override
public User selectUserByName(String username) {
return userMapper.selectUserByName(username);
}
}
在controller包下添加UserController类 和 errorController类:
package com.example.demo.controller;
import com.example.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.websocket.Session;
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/")
public String index(){
return "index";
}
//到登录页面
@RequestMapping("/user/toLogin")
public String toLogin(){
return "/user/login";
}
//到添加页面
@RequestMapping("/user/add")
public String toAddUser(){
return "/user/add";
}
//到更新页面
@RequestMapping("/user/update")
public String toUpdateUser(){
return "/user/update";
}
//验证用户登录
@RequestMapping("/checkLogin")
public String checkLogin(String username, String password, Model model){
System.out.println(username + ":" + password);
if (username.equals("") || password.equals("")){
model.addAttribute("msg","用户名和密码不能为空!");
return "forward:/user/toLogin";
}
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
model.addAttribute("username",username);
return "forward:/";
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名不存在!");
return "forward:/user/toLogin";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","用户名或密码错误!");
return "forward:/user/toLogin";
}
}
//注销用户
@RequestMapping("/logout")
public String logout(){
Subject currentSubject = SecurityUtils.getSubject();
currentSubject.logout();
return "redirect:/";
}
}
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/error")
public class ErrorController {
//跳转到权限不足提示页面
@RequestMapping("/noAuth")
public String toNoAuth(){
return "/error/noauth";
}
}
在shiro包中编写UserRealm:
package com.example.demo.shiro;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
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.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑!");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//获取当前登录对象
Subject subject = SecurityUtils.getSubject();
User user = (User)subject.getPrincipal();
List<String> permissionList = new ArrayList<>();
for (Role role : user.getRoleList()){
permissionList.add(role.getRolename());
}
System.out.println(String.valueOf(permissionList));
simpleAuthorizationInfo.addStringPermissions(permissionList);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑!");
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)authenticationToken;
User user = userService.selectUserByName(usernamePasswordToken.getUsername());
System.out.println(user.toString());
//用户名是否存在
if (!usernamePasswordToken.getUsername().equals(user.getUsername())){
return null;
}
//判断密码,第一个参数是将当前user添加到principal中,以便上面授权的时候获取
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}
在config包下添加ShiroConfig:
package com.example.demo.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.example.demo.shiro.UserRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//创建ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
/** 添加Shiro内置过滤器
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map<String, String> filermap = new LinkedHashMap<>();
filermap.put("/", "anon");
filermap.put("/user/add","perms[管理员]");
filermap.put("/user/update","perms[游客]");
filermap.put("/user/toLogin", "anon");
filermap.put("/user/*", "authc");
//设置登录链接
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
//设置未经授权的访问自动跳转链接
shiroFilterFactoryBean.setUnauthorizedUrl("/error/noAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filermap);
return shiroFilterFactoryBean;
}
//创建DefaultWebSecurityManager
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
//创建Realm
@Bean(name = "userRealm")
public UserRealm getUserRealm(){
return new UserRealm();
}
//整合ShiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect getShirodialect(){
return new ShiroDialect();
}
}
在templates下,目录结构如图:
noauth.html:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>权限不足提示title>
head>
<body>
<h3>对不起,您的权限不足!h3>
<div>div>
<script>
var div = document.querySelector('div');
var timer = 5; //提前先调用以下函数 防止刷新时页面出现空白
skip();
setInterval(skip, 1000);
function skip(){
if(timer == 0){
location.href = '/';
}
div.innerHTML = '您将在' + timer + '钟之后跳转到首页';
timer--;
}
script>
body>
html>
login.html:
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>登录title>
head>
<body>
<h3>登录h3>
<div th:if="${msg} != null">
<label th:text="${msg}" style="color:red;"/>
div>
<form action="/checkLogin" method="post">
用户名:<input type="text" name="username"/> <br/>
密码:<input type="password" name="password"> <br/>
<button type="submit">提交button>
form>
body>
html>
add.html:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户title>
head>
<body>
<h3>添加用户界面!h3>
body>
html>
update.html:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更改用户title>
head>
<body>
<h3>更改用户界面!h3>
body>
html>
index.html:
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>首页title>
head>
<body>
<h3>这是首页!h3>
<div th:if="${username} != null">
<label th:text="${username}">欢迎您,${username}label>
<br/>
<a href="/logout">注销a>
div>
<div th:if="${username} == null">
<a href="/user/toLogin">登录a>
div>
<div shiro:hasPermission="管理员">
<a href="/user/add">添加用户a>
div>
<div shiro:hasPermission="游客">
<a href="/user/update">更新用户a>
div>
body>
html>
数据库:
数据库名称:springboot
3个表分别为user、role、user_role:
CREATE TABLE `user` (
`id` int(32) NOT NULL AUTO_INCREMENT,
`username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
CREATE TABLE `role` (
`id` int(8) NOT NULL AUTO_INCREMENT,
`rolename` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
CREATE TABLE `user_role` (
`id` int(8) NOT NULL AUTO_INCREMENT,
`user_id` int(8) DEFAULT NULL,
`role_id` int(8) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `role_id` (`role_id`),
CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
说明:其实此项目中只需要使用一个表就行,但为了复杂化数据库,故特意弄了3张表。user表与role表之间是多对多关系,将user_role作为一张中间表,实现了role与user_role 和 user 与 user_role的一对多关系。
表中随便加点数据:
至此,项目搭建完成,启动springBoot的启动类,访问:localhost:8080/