添加依赖
<!-- 导入thymeleaf依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</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>2.0.0</version>
<scope>compile</scope>
</dependency>
ShiroConfig类
指定哪些路径可以直接访问,哪些路径需要登录认证后才能访问
package cn.tedu.springbootshiro02.shiro;
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;
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map<String,String> filterMap = new LinkedHashMap<String,String>();
/**设置是controller中的那些路径需要登录认证,哪些不需要登录认证*/
filterMap.put("/index", "anon");//这个路径放行
filterMap.put("/login", "anon");//这个路径放行
filterMap.put("/add", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效
filterMap.put("/update", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效
filterMap.put("/**", "authc");//其他页面需要登录才能访问
//设置需要登录认证才能访问的页面,但未登录直接访问时的重定向请求
shiroFilterFactoryBean.setLoginUrl("/toLogin");//对应的controller中有这个 @RequestMapping("/toLogin")
/**设置授权拦截,访问controller中的/add路径,需要用户添加的权限,访问/update路径,需要用户更新的权限;
授权的具体逻辑在UserRealm类的doGetAuthorizationInfo方法中*/
filterMap.put("/add", "perms[add]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致
filterMap.put("/update", "perms[update]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致
//设置未授权重定向请求
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建Realm
*/
@Bean(name="userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
/**
* 配置ShiroDialect,用户thymeleaf和shiro标签配合使用
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
UserRealm类
doGetAuthenticationInfo方法是具体的执行认证逻辑(与数据库中的数据比对校验)
package cn.tedu.springbootshiro02.shiro;
import cn.tedu.springbootshiro02.entity.User;
import cn.tedu.springbootshiro02.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;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑");
//给当前登录用户 资源授权,授权字符串要跟filterMap.put("/add", "perms[add]");中[]字符串一致
SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
// 获取到当前登录用户 subject
Subject subject = SecurityUtils.getSubject();
//这个principal就是下面登录认证逻辑中return new SimpleAuthenticationInfo(user,user.getPassword(),"");第一个参数user
User user = (User) subject.getPrincipal();
// 根据用户名查询数据库
User dbUser = userService.findByName(user.getName());
// 根据数据库查询到的权限信息,赋予该用户权限
sai.addStringPermission(dbUser.getPerms());
return sai;
}
/**
* 执行认证(登录)逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证(登录)逻辑");
//编写shiro判断逻辑,判断用户名和密码
//1.判断用户名
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
User user = userService.findByName(token.getUsername());
//判断用户输入的用户名跟数据库的用户名是否一致
if(user==null){
//用户名不存在
return null;//shiro底层会抛出UnKnowAccountException
}
//2.判断密码
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
登录认证
controller写法
@RequestMapping("/login")
public String login(String name,String password,Model model){
/**
* 使用Shiro编写认证操作
*/
//1.获取Subject
Subject subject = SecurityUtils.getSubject();
//2.封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(name,password);
//3.执行登录方法
try {
subject.login(token);//将token传给UserRealm类作为doGetAuthenticationInfo方法的参数进行登录验证
//登录成功
return "redirect:/index";//重定向到controller请求/index地址
} catch (UnknownAccountException e) {//登录失败:用户名不存在
model.addAttribute("msg", "用户名不存在");
return "login";//转发到到templates目录下对应的html页面
}catch (IncorrectCredentialsException e) {//登录失败:密码错误
model.addAttribute("msg", "密码错误");
return "login";
}
}
UserRealm中认证逻辑
/**
* 执行认证(登录)逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证(登录)逻辑");
//编写shiro判断逻辑,判断用户名和密码
//1.判断用户名
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;//就是controller中 登录时传过来的token:subject.login(token); token中携带用户名和密码
User user = userService.findByName(token.getUsername());
//判断用户输入的用户名跟数据库的用户名是否一致
if(user==null){
//用户名不存在
return null;//shiro底层会抛出UnKnowAccountException
}
//2.判断密码
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
授权
1、在ShiroConfig类中指定哪些路径需要什么样的权限才能访问
filterMap.put("/add", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效
filterMap.put("/update", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效
filterMap.put("/add", "perms[add]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致
filterMap.put("/update", "perms[update]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致
2、在UserRealm类中的doGetAuthorizationInfo方法中赋予当前登录的用户什么样的权限
/**
* 执行授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑");
//给当前登录用户 资源授权,授权字符串要跟ShiroConfig类中filterMap.put("/add", "perms[add]");的[]字符串一致
SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
// 获取到当前登录用户 subject
Subject subject = SecurityUtils.getSubject();
//这个principal就是下面登录认证逻辑中return new SimpleAuthenticationInfo(user,user.getPassword(),"");第一个参数user
User user = (User) subject.getPrincipal();
// 根据用户名查询数据库
User dbUser = userService.findByName(user.getName());
// 根据数据库查询到的权限信息,赋予该用户权限
sai.addStringPermission(dbUser.getPerms());
return sai;
}
ShiroConfig类中的权限拦截规则也可以设置为基于注解的权限拦截
1、在shiroConfig类中添加
/**
* 开启shiro注解支持,例如@RequiresRoles()和@RequiresPermissions()
* shiro需要结合Spring的aop实现
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator dapc = new DefaultAdvisorAutoProxyCreator();
dapc.setProxyTargetClass(true);
return dapc;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
AuthorizationAttributeSourceAdvisor asa = new AuthorizationAttributeSourceAdvisor();
asa.setSecurityManager(defaultWebSecurityManager);
return asa;
}
2、在controller中,添加授权异常的拦截方法
/**
自定义异常拦截,没有授权的异常,跳转到请求 @RequestMapping("/unAuth")
当shiro出现权限验证失败后会抛出异常,因此必须写一个自定义的异常拦截,否则无法正常转发我们的没授权页面unAuth.html
*/
@ExceptionHandler(value = {UnauthorizedException.class})
public String unPerssion(Throwable throwable){
return "unAuth";
}
3、在请求的资源上添加注解
/**
* @RequiresRoles(value = {"admin"})
* @RequiresPermissions(value = {"add","update"})
* 是shiro的注解,用来访问该方法或者类需要什么角色
*/
@RequiresRoles(value = {"admin"})
@RequiresPermissions(value = {"add","update"})
@RequestMapping("/admin/hello")
@ResponseBody
public String hello(Model model){
return "Admin下面的哈喽!";
}