开发工具:Spring Tool Suite(TST)
Maven :3.3.9
jdk:1.8
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
com.vae
shiro
0.0.1-SNAPSHOT
springboot-shiro
Demo project for Spring Boot
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
true
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
package com.vae.user.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RequestMapping
public class UserController {
@RequestMapping("/hi")
public String hi(){
return "hi shiro";
}
}
package com.vae;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* springboot启动类
* @author vae
*
*/
@SpringBootApplication
public class SpringbootShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootShiroApplication.class, args);
}
}
org.springframework.boot
spring-boot-starter-thymeleaf
下面写了两种方式,第一种比较直观和优雅,第二种相对普遍且代码较少,且迎合从struts2
注意:第二种方式返回String类型时,controller层不可以使用
@RestController ,@RestController 等价于 @Controller 加上 @ResponseBody, @ResponseBody表示该方法的返回不会被解析为跳转, 而是直接写入http响应正文。
@RequestMapping("/index")
public ModelAndView index() {
ModelAndView view = new ModelAndView();
// 设置跳转的视图 默认映射到 src/main/resources/templates/{viewName}.html
view.setViewName("index");
// 设置属性
view.addObject("title", "我的templates页面");
view.addObject("desc", "欢迎进入我的csdn博客");
Author author = new Author();
author.setAge(18);
author.setEmail("[email protected]");
author.setName("vae");
view.addObject("author", author);
return view;
}
@RequestMapping("/index1")
public String index1(HttpServletRequest request) {
// TODO 与上面的写法不同,但是结果一致。
// 设置属性
request.setAttribute("title", "我的templates页面");
request.setAttribute("desc", "欢迎进入我的csdn博客");
Author author = new Author();
author.setAge(18);
author.setEmail("[email protected]");
author.setName("vae");
request.setAttribute("author", author);
// 返回的 index 默认映射到 src/main/resources/templates/xxxx.html
return "index";
}
class Author {
private int age;
private String name;
private String email;
// 省略 get set
}
可以看到 thymeleaf
是通过在标签中添加额外属性动态绑定数据的
Title
Hello World
=====作者信息=====
Subject:用户主体(关联SecurityManager,把操作交给SecurityManager)
SecurityManager:安全管理器(关联Realm)
Realm:shiro连接数据库的桥梁
(1)添加shiro和spring整合依赖
org.apache.shiro
shiro-spring
1.4.0
(2)自定义Realm类
package com.vae.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.vae.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";
}
进入用户新增页面:用户新增
进入用户更新页面:用户更新
测试可访问。
@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页面
Filter | 解释 |
---|---|
anon | 无参,开放权限,可以理解为匿名用户或游客 |
authc | 无参,需要认证 |
logout | 无参,注销,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url |
authcBasic | 无参,表示 httpBasic 认证 |
user | 无参,表示必须存在用户,当登入操作时不做检查 |
ssl | 无参,表示安全的URL请求,协议为 https |
perms[user] | 参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms[“user, admin”],当有多个参数时必须每个参数都通过才算通过 |
roles[admin] | 参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles[“admin,user”],当有多个参数时必须每个参数都通过才算通过 |
rest[user] | 根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等 |
port[8081] | 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString 其中 schmal 是协议 http 或 https 等等,serverName 是你访问的 Host,8081 是 Port 端口,queryString 是你访问的 URL 里的 ? 后面的参数 |
(1)在templates下新增login.html页面
(2)在ShiroConfig中shiroFilterFactoryBean方法中修改拦截后跳转的页面
//修改跳转的登录页面,不加此项就会跳转到login.jsp页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
(3)在UserController中添加toLogin方法
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
测试,当没有权限跳转到该login.html页面
filterMap.put("/index", "anon");
filterMap.put("/*", "authc");
登录页面
登录页面
@RequestMapping("/login")
public String login(String username,String password,Model model) {
/**
* 使用shiro编写认证操作
*/
//获取Subject
Subject subject=SecurityUtils.getSubject();
//封装用户数据
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//执行登录方法
try {
//只要执行login方法,就会去执行UserRealm中的认证逻辑
subject.login(token);
//如果没有异常,代表登录成功
//跳转到textThymeleaf页面,代表主页
return "redirect:/index";
} 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="vae";
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会自动判断
}
com.alibaba
druid
1.1.10
mysql
mysql-connector-java
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.1.1
(1)创建数据库及用户表
mysql> CREATE DATABASE springboot_shiro;
Query OK, 1 row affected
mysql> use springboot_shiro;
Database changed
mysql> CREATE TABLE `user` (
`id` int(11) NOT NULL COMMENT 'id',
`username` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(20) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT '密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected
(2)配置application.properties
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_shiro?characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
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.vae.user.entity;
public class User {
private Integer id;
private String username;
private String password;
//省略get和set方法
}
package com.vae.user.dao;
import com.vae.user.entity.User;
public interface UserDao {
/**
* 根据username查找用户信息
* @param username
* @return
*/
public User findByName(String username);
}
id, username, password
package com.vae.user.service;
import com.vae.user.entity.User;
public interface UserService {
/**
* 根据username查找用户信息
* @param username
* @return
*/
public User findByUsername(String username);
}
package com.vae.user.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.vae.user.dao.UserDao;
import com.vae.user.entity.User;
import com.vae.user.service.UserService;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
public User findByUsername(String username) {
return userDao.findByUsername(username);
}
}
@MapperScan("com.vae.**.dao")
@Autowired
private UserService userService;
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("执行认证逻辑");
// 编写shiro判断逻辑,判断用户名和密码
System.out.println(arg0.toString());
// 1. 判断用户名
UsernamePasswordToken token = (UsernamePasswordToken) arg0;
User user = userService.findByUsername(token.getUsername());
if (user==null) {
//用户名不存在
return null;//shiro底层会抛出UnknownAccountException
}
// 2. 判断密码
// 参数1:需要返回给login方法的数据;参数2:数据库密码,shiro会自动判断
return new SimpleAuthenticationInfo("", user.getPassword(), "");
}
效果相同。
//授权过滤器:授权拦截后,shiro会自动跳转到未授权页面
filterMap.put("/add", "perms[user:add]");
filterMap.put("/*", "authc");
Tips:注意要写在/*之前,否则不会拦截
(1)ShiroConfig中
//修改自动跳转的未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth");
(2)UserController中
@RequestMapping("/unAuth")
public String unAuth() {
return "unAuth";
}
(3)添加unAuth.html
未授权页面
抱歉!您无权限访问!
登录认证之后,访问/add页面会提示未授权,而/update可以正常访问。
刚才打印的log日志中可以看到,只要访问了需要授权访问的资源,就会执行UserRealm中的doGetAuthenticationInfo()方法,在该方法中给资源进行授权。
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("执行授权逻辑");
//给资源进行授权
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//添加资源的授权字符串
info.addStringPermission("user:add");
return info;
}
测试查看效果:日志中可以看到执行了该授权逻辑,现在可以访问/add了
给user表添加perms字段,插入两个测试用户
(1)User.java:添加perms属性和getter/setter
(2)UserDao.java:
public User findById(Integer id);
(3)UserMapper.xml
(4)UserService.java
public User findById(Integer id);
(5)UserServiceImpl.java
@Override
public User findById(Integer id) {
return userMapper.findById(id);
}
(6)给/update添加资源拦截
filterMap.put("/update", "perms[user:update]");
3. 修改UserRealm中的doGetAuthorizationInfo方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("执行授权逻辑");
//给资源进行授权
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
/*//添加资源的授权字符串
info.addStringPermission("user:add");*/
//获取当前用户
Subject subject=SecurityUtils.getSubject();
User user=(User)subject.getPrincipal();
//到数据库查询当前登录用户的授权字符串
User dbUser=userService.findById(user.getId());//通过当前登录用户id查找的数据库用户
info.addStringPermission(dbUser.getPerms());
return info;
}
将doGetAuthenticationInfo()方法的返回修改为
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
因为User user=(User)subject.getPrincipal(); 所取得的当前登录用户就是从这里来的
各自有了各自的权限。
com.github.theborakompanioni
thymeleaf-extras-shiro
2.0.0
ShiroConfig中
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
进入用户新增页面:用户新增
进入用户更新页面:用户更新
不同权限用户登录,只显示了他有权限看到的内容。
借鉴地址:SpringBoot与Shiro整合-权限管理实战视频笔记
重新整理了一边