Shiro 概述
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
更多理论知识,可以去他人博客查阅,这里博主直接贴代码,里面有一些注释,希望会对你有帮助。
Demo
依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wm</groupId>
<artifactId>10-shiro-login</artifactId>
<version>1.0.0</version>
<name>10-shiro-login</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--启动-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--thymeleaf拓展shiro标签-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
springboot配置
spring:
datasource:
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.136.136:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&&serverTimezone=GMT%2B8
thymeleaf:
cache: false
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Dao层
package com.wm.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wm.pojo.User;
import org.apache.ibatis.annotations.Mapper;
/**
* @Author: sshdg
* @Date: 2020/9/15 10:02
*/
@Mapper
public interface UserDao extends BaseMapper<User> {
}
service层
package com.wm.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.wm.dao.UserDao;
import com.wm.pojo.User;
import com.wm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: sshdg
* @Date: 2020/9/15 10:16
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Override
public User login(String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
User user1 = userDao.selectOne(wrapper);
return user1;
}
}
ShiroConfig
package com.wm.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
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;
/**
* @Author: sshdg
* @Date: 2020/9/15 16:33
*/
@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
Map<String, String> map = new LinkedHashMap<>();
// 向map中添加 过滤的路径 和 必须认证才可以访问的过滤器,此过滤器拦截成功会默认跳转到"login.jsp"
// map.put("/add", "authc");
// 也可以设置全部拦截,一些不拦截
map.put("/index", "anon");
map.put("/", "anon");
map.put("/login", "anon");
map.put("/logout", "logout");
// []方括号中放入的是realm中设置的授权码
// 一般来说,user会有一个权限属性,我这里是根据用户名来判断
map.put("/add", "perms[admin]");
//这行代码要放在最后一部添加,不然所有请求都会拦截
map.put("/**", "authc");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//设置拦截后跳转到的登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置无权访问页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Reaml
*/
@Bean(name = "userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
Realm
package com.wm.config;
import com.wm.pojo.User;
import com.wm.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;
/**
* @Author: sshdg
* @Date: 2020/9/15 16:48
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行-》授权 doGetAuthorizationInfo");
//这里注意,要是SimpleAuthorizationInfo类型。“Authorization”意思是授权,而非“Authentication 认证”
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
//这里的getPrincipal就是认证方法中返回值的第一个参数:user
User user = (User) subject.getPrincipal();
//设置授权码,perms中的授权码和这个保持一致才有权限进入
info.addStringPermission(user.getUsername());
return info;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行-》认证 doGetAuthenticationInfo");
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
User user = userService.login(token.getUsername());
//将用户放入session
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute("user", user);
if (user == null){
//用户名不存在,return null会抛出UnknownAccountException异常
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}
controller层
package com.wm.controller;
import com.wm.pojo.User;
import com.wm.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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @Author: sshdg
* @Date: 2020/9/15 11:09
*/
@Controller
public class UserController {
@Autowired
UserService userService;
@GetMapping("/login")
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String login(String username, String password, Model model){
// 1、获取subject
Subject subject = SecurityUtils.getSubject();
// 2、封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 3、执行登录方法
try {
subject.login(token);
// 登录成功
return "/index";
} catch (UnknownAccountException e) {
// 一旦有异常就是登录失败
// UnknownAccountException用户名不存在异常
model.addAttribute("msg", "用户名不存在");
return "/login";
} catch (IncorrectCredentialsException e){
// 密码错误异常
model.addAttribute("msg", "密码错误");
return "/login";
}
}
@GetMapping("/index")
public String indexPage(){
return "index";
}
@GetMapping("/add")
public String addPage(){
return "add";
}
@GetMapping("/info")
public String infoPage(){
return "info";
}
@GetMapping("/unAuth")
public String unAuthPage(){
return "unAuth";
}
}
前端页面
add页面为管理员才能访问,index首页,info信息页登录就可访问,unAuth无权访问页
index页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<div th:if="${session.user}==null">
<a th:href="@{/login}">登录页面</a><br>
</div>
<div th:if="${session.user}!=null">
<a th:href="@{/logout}">退出登录</a><br>
</div>
<div shiro:hasPermission="admin">
<a th:href="@{/add}">管理员才可以进入添加页面</a><br>
</div>
<a th:href="@{/info}">登录就可以进入信息页面</a><br>
</body>
</html>
登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<div th:text="${msg}"></div>
<form th:action="@{/login}" th:method="post">
<input th:type="text" th:name="username" placeholder="username">
<input th:type="text" th:name="password" placeholder="username">
<input th:type="submit" value="登录">
</form>
</body>
</html>