pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--shiro的jar包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--thymeleaf 页面配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
自定义shiro的安全管理器配置
package com.jk.shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro的配置文件类 相当与之前的spring.xml配置文件
*/
@Configuration//声明当前类是一个spring配置文件类
public class ShiroConfig {
//@Bean 相当于 <bean name="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor">
// </bean>
//LifecycleBeanPostProcessor将Initializable和Destroyable的实现类统一在其内部自动分别调用了
// Initializable.init()和Destroyable.destroy()方法,从而达到管理shiro bean生命周期的目的
@Bean(name = "lifecycleBeanPostProcessor")//shiro和spring bean之间的结合类
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
//shiro 安全管理器 shiro的主入口
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
// 注入缓存管理器;
//securityManager.setCacheManager(ehCacheManager());
//在安全管理器中 注入realm.数据源
securityManager.setRealm(myShiroRealm());
//securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
//shiro的主要核心过滤器
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
//创建一个shiro过滤器工厂
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
//shiro过滤器链 按照过滤器链顺序执行过滤内容
//logout 退出登录 /logout当前项目中的退出路径 logout是shiro的退出登录过滤器
//当页面访问退出登录路径时 会进入shiro退出登录拦截器 自动退出登录
filterChainDefinitionMap.put("/logout", "logout");
//anon 放过权限拦截过滤器 不需要权限也能访问的资源
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/gifCode", "anon");
//toLogin 跳转到登录页面请求
filterChainDefinitionMap.put("/login", "anon");
//除上面放过拦截的路径之外 其余路径都会被拦截
// authc权限拦截过滤器 会走认证和授权 查看当前认证有无通过 是否有权限访问
filterChainDefinitionMap.put("/**", "authc");
//LoginUrl 设置登录请求路径 访问此路径 会进入shiro的认证器方法 根据页面传递的用户名密码进行认证
//当认证失败之后还会调用此路径 调用Controller中的login方法 提示用户 用户名错误还是密码错误
shiroFilterFactoryBean.setLoginUrl("/toLogin");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/toIndex");
// 未授权界面;
//shiroFilterFactoryBean.setUnauthorizedUrl("/warning");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("注入成功");
return shiroFilterFactoryBean;
}
//注入数据源 (认证、授权类)
@Bean
@DependsOn("lifecycleBeanPostProcessor")//@DependsOn保证lifecycleBeanPostProcessor在它之前执行
public MyRealm myShiroRealm(){
MyRealm myShiroRealm = new MyRealm();
return myShiroRealm;
}
//为当前shiro配置aop切面 使shiro与springAOP结合
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
//设置当前aop动态代理为cglib代理
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
//ProxyTargetClass 代理目标(实例) 类 把当前代理目标设置为类代理 默认为interface接口代理
daap.setProxyTargetClass(true);
return daap;
}
//thymlef和shiro整合时需要注入此类来完成权限验证
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
配置realm,认证和授权,继承AuthorizingRealm类,实现AuthorizationInfo授权和AuthenticationInfo
package com.jk.shiro;
import com.jk.model.Menu;
import com.jk.model.User;
import com.jk.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.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//授权器
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//User user = (User) principals.getPrimaryPrincipal();
User user = (User) SecurityUtils.getSubject().getPrincipal();
System.out.println(user.getId());
//创建一个简单的授权器
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//获取用户角色集
List<String> roleList = userService.getRoleByUserId(user.getId());
Set<String> set = new HashSet<>(roleList);
simpleAuthorizationInfo.setRoles(set);
// 获取用户权限集
List<Menu> permissionList = userService.getUserMenuAll(user.getId());
List<String> permissions = new ArrayList<>();
for (Menu menu: permissionList) {
// 处理用户多权限 用逗号分隔
if (StringUtils.isEmpty(menu.getPerms())){
continue;
}
permissions.add(menu.getPerms());
System.out.println(menu.getPerms());
}
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
//认证器 登录
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token 令牌 token.getPrincipal() 获取到前台页面输入的用户名
// 获取用户输入的用户名和密码
String userName = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
User user = userService.getUserByName(userName);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
//创建一个简单认证器 第一个参数为当前登录用户的主体 可以是用户名 也可以是用户对象 一般都是用户对象
//第二个参数为数据库查询出来的密码 认证器会用前台传递的密码后查询出来的密码对比
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
//super.clearCachedAuthorizationInfo(simpleAuthenticationInfo.getPrincipals());
// SecurityUtils.getSubject().getSession().setAttribute("login",user);
//MD5 加密+加盐+多次加密
//SimpleAuthenticationInfo authcInfo = new SimpleAuthenticationInfo(adminUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
}
控制层登录调用的方法
//这里ResponseBo是自定义返回类,类似Map
@PostMapping("/login")
@ResponseBody
public ResponseBo login(User user, String code, Boolean rememberMe,HttpServletRequest request) {
if (StringUtils.isEmpty(code)) {
return ResponseBo.warn("验证码不能为空!");
}
HttpSession session = request.getSession();
String sessionCode = (String) session.getAttribute(CODE_KEY);
if (!code.equalsIgnoreCase(sessionCode)) {
return ResponseBo.warn("验证码错误!");
}
String password = user.getPassword();
// 密码 MD5 加密
//password = MD5Utils.encrypt(user.getLoginNumber().toLowerCase(), password);
UsernamePasswordToken token = new UsernamePasswordToken(user.getLoginNumber(), password, rememberMe);
try {
Subject subject = getSubject();
if (subject != null)
subject.logout();
subject.login(token);//调用此方法会进认证器进行认证
//this.userService.updateLoginTime(username);
return ResponseBo.ok();
} catch (UnknownAccountException | IncorrectCredentialsException | LockedAccountException e) {
return ResponseBo.error(e.getMessage());
} catch (AuthenticationException e) {
return ResponseBo.error("认证失败!");
}
}
数据库的配置结构:
在类上加@@RequiresPermissions("数据库定义的权限标识名")
@RequestMapping("user/getUser")
@ResponseBody
@RequiresPermissions("user:list")//shiro授权基于aop实现
public List<User> getUser(){
List<User> userList = new ArrayList<>();
try {
userList = userService.getUser();
}catch(AuthorizationException e){
}
return userList;
}
页面按钮加shiro:hasPermission="数据库定义的按钮标识名"
<!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>
<link rel="stylesheet" href="/login/js/easyui/themes/icon.css"/>
<link rel="stylesheet" href="/login/js/easyui/themes/default/easyui.css"/>
<link rel="stylesheet" href="/login/js/easyui/themes/color.css"/>
<script src="/login/js/jquery.min.js"></script>
<script src="/login/js/easyui/jquery.easyui.min.js"></script>
<script src="/login/js/easyui/locale/easyui-lang-zh_CN.js"></script>
</head>
<body>
<!--hasPermission 判断是否拥有权限,拥有则显示html标签,未拥有则不展示-->
<div id="userToolbar">
<a shiro:hasPermission="user:add" >新增</a>
<a shiro:hasPermission="user:update" href="#">修改</a>
<a shiro:hasPermission="user:delete" href="#">删除</a>
<a shiro:hasPermission="user:delete" href="javascript:logout()">注销</a>
</div>
<table id="userTable"></table>
<script>
function addUser(aa){
}
function logout(){
location.href="../logout";
}
function loadList(){
$("#userTable").datagrid({
url:"/user/getUser",
toolbar:"#userToolbar",
columns:[
[
{field:"id",title:"ID"},
{field:"name",title:"ID"},
{field:"loginNumber",title:"ID"},
{field:"password",title:"ID"},
{field:"cc",title:"操作"},
]
]
})
}
$(function(){
loadList();
})
</script>
</body>
</html>
ok,大功告成。