Spring Boot + Shiro + thymeleaf 整合,以及一些页面标签的使用
shiro的功能介绍以及一些特点之类的这里就不介绍了,百度下就ok了,这里只是介绍如何与spring boot进行整合。
后续还有一些关于shiro加密、多realm验证、缓存替换为redis等等。
springboot版本:
<version>1.5.20.RELEASE</version>
<!--spring的aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
<!--shiro与spring的整合依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>1.2.1</version>
</dependency>
<!-- Shiro-redis插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
java代码:
ShiroConfig.java
package com.qw.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.qw.shiro.FilterChainDefinitionMapBuilder;
import com.qw.shiro.UserRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* shiro配置类
*/
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
@Autowired
private FilterChainDefinitionMapBuilder mapBuilder;
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置 SecurityManager
bean.setSecurityManager(securityManager);
// 设置登录页面的Url
bean.setLoginUrl("/login/listPage");
// 登录成功时访问的Url
bean.setSuccessUrl("/admin/home/page/index");
// 没有权限时访问的url
bean.setUnauthorizedUrl("/login/unauthorizedUrl");
bean.setFilterChainDefinitionMap(mapBuilder.buildFilterChainDefinitionMap());
return bean;
}
//使用shiro页面标签
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
FilterChainDefinitionMapBuilder:从数据库中获取全部权限的url。
package com.qw.shiro;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
@Component
public class FilterChainDefinitionMapBuilder {
//查询数据库中的所有url权限。权限必须要有顺序的写。
public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){
/**
* anon:匿名用户可访问
* authc:认证用户可访问
* user:使用rememberMe可访问
* perms:对应权限可访问
* role:对应角色权限可访问
**/
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
//静态资源
filterMap.put("/jquery/**", "anon");
filterMap.put("/lay-config/**", "anon");
filterMap.put("/layui-v2.3.0/**", "anon");
filterMap.put("/layui-v2.5.5/**", "anon");
filterMap.put("/layui_extends/**", "anon");
filterMap.put("/utils/**", "anon");
filterMap.put("/wangEditorJS/**", "anon");
//登陆的url
filterMap.put("/login/login", "anon");
//从数据库中查询出所有需要权限才可以访问的url。
filterMap.put("/admin/home/page/index","perms[/admin/home/page/index]");
filterMap.put("/admin/home/page/user","perms[/admin/home/page/user]");
filterMap.put("/admin/home/page/role","perms[/admin/home/page/role]");
filterMap.put("/**","authc");
return filterMap;
}
}
UserRealm:实现登陆用户的认证(即登陆)和鉴权(即授权)
package com.qw.shiro;
import com.qw.model.User;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import java.util.*;
/**
* 自定义Realm,自己注入bean进行获取用户权限信息
*/
@Slf4j
@Component
public class UserRealm extends AuthorizingRealm {
//授权,即查询权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1. 从 PrincipalCollection 中来获取登录用户的信息,获取的是从认证,即登陆接口中保存的信息。
User user = (User) principals.getPrimaryPrincipal();
//2、通过用户信息去数据库查询其权限,也可以再认证的时候查询,保存再SimpleAuthenticationInfo 里面,在PrincipalCollection 中取出。
//这里就直接写死了。
//用户的角色信息
Set<String> roleSet = new HashSet<>();
roleSet.add("超级管理员");
//用户的所有url权限信息
Set<String> urlSet = new HashSet<>();
urlSet.add("/admin/home/page/index");
urlSet.add("/admin/home/page/role");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roleSet);
info.addStringPermissions(urlSet);
return info;
}
//认证,即登陆
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
log.info("username = {},password = {}",username,password);
//根据登陆账号,查出数据库中的用户信息,这里我就不写了。
User user = new User();
user.setId(1L);
user.setName("admin");
user.setPassword("e10adc3949ba59abbe56e057f20f883e");
if (user == null){
throw new UnknownAccountException("用户不存在!");
}
//异常类型有:可以根据比对,自行抛出异常。
//UnknownAccountException:用户不存在
// IncorrectCredentialsException:若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。
// LockedAccountException 用户被锁定的异常 LockedAccountException
// AuthenticationException 所有认证时异常的父类.
//把用户信息存储,这里可以放入用户对象,可以方便上面的授权方法直接获取对象
//shiro是对密码的比对,不会比对用户名。
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
return info;
}
}
controller
LoginController
package com.qw.controller;
import com.qw.utils.RspFailMessage;
import com.qw.utils.RspMessage;
import com.qw.utils.RspSuccessMessage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Api(tags = "登陆")
@Slf4j
@Controller
@RequestMapping("/login")
public class LoginController {
@ApiOperation("登陆页面")
@GetMapping("/listPage")
public String listPage(){
return "login";
}
@ResponseBody
@ApiOperation("登陆")
@ApiImplicitParams({
@ApiImplicitParam(name = "account", value = "账号", paramType = "query", dataType = "String"),
@ApiImplicitParam(name = "password", value = "密码", paramType = "query", dataType = "String")
})
@PostMapping("/login")
public RspMessage loginIn(String account,String password) {
UsernamePasswordToken token = new UsernamePasswordToken(account, password);
//当前正在运行的主体,里面包含了,用户是否登陆、是否具有某种角色,具有某种权限等等。
Subject subject = SecurityUtils.getSubject();
try {
System.out.println("用户是否登陆:"+subject.isAuthenticated());
subject.login(token);//登陆
System.out.println("用户是否登陆:"+subject.isAuthenticated());
} catch (UnknownAccountException uae ) {
//用户名不在系统中,显示错误信息?
return new RspFailMessage("","用户或密码错误");
} catch (IncorrectCredentialsException ice ) {
//密码不匹配,请重试?
return new RspFailMessage("","用户或密码错误");
} catch (LockedAccountException lae ) {
//该用户名的帐户被锁定-无法登录。给他们看信息?
return new RspFailMessage("","账号被锁定,请联系管理员");
} catch (AuthenticationException ae ) {
//意外条件-错误?
return new RspFailMessage("","账号不存在");
}
return new RspSuccessMessage();
}
@ApiOperation("登出")
@GetMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login/listPage";
}
@ApiOperation("没有权限的页面")
@GetMapping("/unauthorizedUrl")
public String unauthorizedUrl(){
return "unauthorized";
}
}
HomeController
package com.qw.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Api(tags = "首页")
@Slf4j
@Controller
@RequestMapping("/admin")
public class HomeController {
@ApiOperation("首页页面")
@GetMapping("/home/page/index")
public String index(){
return "shiro/index";
}
@ApiOperation("用户页面")
@GetMapping("/home/page/user")
public String user(){
return "shiro/user";
}
@ApiOperation("角色页面")
@GetMapping("/home/page/role")
public String role(){
return "shiro/role";
}
}
html页面
登陆页面由于是layui写的,这里就不写了,谁便写个ajax提交就可以了。
用户页面、角色页面、没有权限页面,就是一个单纯的html页面,没有什么内容,只是用来标识下,是否跳转了而已。
登陆成功跳转的首页:index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
首页
<br/>
<br/>
<!--如果当前用户的角色不是超级管理员,就不显示它-->
<shiro:hasRole name="超级管理员">
<!--显示登陆用户在reaml中存入的信息,就是保存在 SimpleAuthenticationInfo 类里面的信息-->
用户名称:<shiro:principal shiro:principal="username"></shiro:principal>
</shiro:hasRole>
<br/><br/>
<shiro:hasPermission name="/admin/home/page/user">
<a href="/admin/home/page/user">用户页面</a>
</shiro:hasPermission>
<br/>
<br/>
<!--如果当前用户没有这个权限,就不显示它-->
<shiro:hasPermission name="/admin/home/page/role">
<a href="/admin/home/page/role">角色页面</a>
</shiro:hasPermission>
<br/>
<br/>
<a href="/login/logout">登出</a>
</body>
</html>