本文内容大部分来自黑马视频的SpringBoot与Shiro整合-权限管理实战视频,在此记录为个人学习笔记。
可按步骤操作,无法实操的实战blog都是耍流氓。
(1)导入shiro和spring整合依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
(2)自定义Realm类
package com.fukaiit.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;
public class UserRealm extends AuthorizingRealm{
/** * 执行授权逻辑 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("执行授权逻辑");
return null;
}
/** * 执行认证逻辑 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("执行认证逻辑");
return null;
}
}
(3)编写shiro配置类(基本结构)
package com.fukaiit.shiro;
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;
/** * shiro的配置类 * @author Administrator * */
@Configuration
public class ShiroConfig {
/** * 创建ShiroFilterFactoryBean */
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
/** * 创建DefaultWebSecurityManager */
@Bean(name="securityManager")
public DefaultWebSecurityManager getdefaultDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//关联Realm
securityManager.setRealm(userRealm);
return securityManager;
}
/** * 创建Realm */
@Bean(name="userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
}
@RequestMapping("/add")
public String add() {
return "user/add";
}
@RequestMapping("/update")
public String update() {
return "user/update";
}
进入用户新增页面:<a href="add">用户新增a>
进入用户更新页面:<a href="update">用户更新a>
测试可访问。
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro内置过滤器,实现权限相关的url拦截
/** * 常见过滤器: * anon:无需认证(登录)可以访问 * authc:必须认证才可以访问 * user:如果使用Remember Me的功能,可以直接访问 * perms:该资源必须得到资源权限才可以访问 * role:该资源必须得到角色权限才可以访问 */
Map filterMap=new LinkedHashMap();
filterMap.put("/add", "authc");
filterMap.put("/update", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
拦截之后重启,再次访问发现:试图访问add或者update,会自动跳转到login.jsp页面
(1)在templates下新增login.html页面
(2)在ShiroConfig中shiroFilterFactoryBean方法中修改拦截后跳转的页面
//修改跳转的登录页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
(3)在UserController中添加toLogin方法
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
测试,当没有权限跳转到该login.html页面
filterMap.put("/testThymeleaf", "anon");
filterMap.put("/*", "authc");
<html>
<head>
<meta charset="UTF-8">
<title>登录页面title>
head>
<body>
<h1>登录页面h1>
<span style="color:red" th:text="${msg}">span>
<form method="post" action="login">
<input type="text" name="username" /><br>
<input type="password" name="password"><br>
<input type="submit" name="submit" value="登录">
form>
body>
html>
@RequestMapping("/login")
public String login(String usernam,String password,Model model) {
/** * 使用shiro编写认证操作 */
//获取Subject
Subject subject=SecurityUtils.getSubject();
//封装用户数据
UsernamePasswordToken token=new UsernamePasswordToken(usernam,password);
//执行登录方法
try {
//只要执行login方法,就会去执行UserRealm中的认证逻辑
subject.login(token);
//如果没有异常,代表登录成功
//跳转到textThymeleaf页面,代表主页
return "redirect:/testThymeleaf";
} catch (UnknownAccountException e) {
e.printStackTrace();
//登录失败
model.addAttribute("msg","用户名不存在");
return "login";
}catch (IncorrectCredentialsException e) {
e.printStackTrace();
model.addAttribute("msg","密码错误");
return "login";
}
}
测试发现,并没有进入/login请求,是因为之前写的拦截器(/*)拦截了所有请求,再对/login请求放行filterMap.put("/login", "anon");
重启测试,发现执行了认证逻辑,返回了用户名不存在异常。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("执行认证逻辑");
//先写模拟数据进行验证,下一步再连接数据库,假设数据库的用户名和密码如下
String dbusername="fukaiit";
String dbpassword="123456";
//编写shiro判断逻辑,判断用户名和密码
//1. 判断用户名
UsernamePasswordToken token=(UsernamePasswordToken) arg0;
if (!token.getUsername().equals(dbusername)) {
//用户名不存在
return null;//shiro底层会抛出UnknownAccountException
}
//2. 判断密码
return new SimpleAuthenticationInfo("",dbpassword,"");//参数1:需要返回给login方法的数据;参数2:数据库密码,shiro会自动判断
}
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.9version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.1.1version>
dependency>
(1)创建用户表
-- 创建用户表
CREATE TABLE USER( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20), PASSWORD VARCHAR(20) );
(2)配置application.properties
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot-shiro
spring.datasource.username=root
spring.datasource.password=root
# 连接池配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# mybatis 别名扫描
mybatis.type-aliases-package=com.fukaiit.domain
package com.fukaiit.domain;
public class User {
private Integer id;
private String username;
private String password;
//getter and setter methods
}
package com.fukaiit.mapper;
import com.fukaiit.domain.User;
public interface UserMapper {
public User findByUsername(String username);
}
<mapper namespace="com.fukaiit.mapper.UserMapper">
<select id="findByUsername" parameterType="String" resultType="User" >
select id,username,password from user where username = #{value}
select>
mapper>
Tips:
1. xml的名字必须与接口文件的名字一致;
2. mapper中的namespace必须与接口类的全名一致
package com.fukaiit.service;
import com.fukaiit.domain.User;
public interface UserService {
public User findByUsername(String username);
}
package com.fukaiit.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fukaiit.domain.User;
import com.fukaiit.mapper.UserMapper;
import com.fukaiit.service.UserService;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
}
@MapperScan("com.fukaiit.mapper")
@Autowired
private UserService userService;
/** * 执行认证逻辑 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("执行认证逻辑");
//编写shiro判断逻辑,判断用户名和密码
//1. 判断用户名
UsernamePasswordToken token=(UsernamePasswordToken) arg0;
User user = userService.findByUsername(token.getUsername());
if (user==null) {
//用户名不存在
return null;//shiro底层会抛出UnknownAccountException
}
//2. 判断密码
return new SimpleAuthenticationInfo("",user.getPassword(),"");//参数1:需要返回给login方法的数据;参数2:数据库密码,shiro会自动判断
}
效果同上节最后。