什么是Shiro?
一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。
Shiro Apache Shiro是一个java安全框架
可以非常容易开发出足够好的应用, 其不仅可以用在javaSE环境,也可以用在javaEE环境。
可以完成认证 ,授权 ,加密等功能
源码:
使用 Shiro
可以 下载它 进入官网首页点击下载 或者去GitHub上下载源码和查看
官方快速入门案例分析:
以下几个核心的东西
/获取当前的用户对象
Subject currentUser = SecurityUtils.getSubject();
//通过当前用户拿到session
Session session = currentUser.getSession();
//往session里存值
session.setAttribute("someKey", "aValue");
//取值
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Subject==>session[" + value + "]");
}
//判断当前的用户是否被认证 isAuthenticated认证
currentUser.isAuthenticated()
currentUser.getPrincipal()
//判断角色
currentUser.hasRole("schwartz")
//获得当前用户权限
currentUser.isPermitted()
//注销
currentUser.logout();
shiro三大对象:
subject :用户
securityManager:管理所有用户
Realm :连接数据
使用Shiro
1.导入shiro依赖
<!--shiro整合spring的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
第二步:编写配置核心类config
先编写一个自定义的Realm子类
先编写Realm是因为securityManager需要依赖Realm
**
* 自定义的UserRealm
* 写自定义的需要继承AuthorizingRealm
* 实现方法 授权跟认证
* @create: 2021/4/25
* @author: Tony Stark
*/
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("----执行了授权-----");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("----执行了认证-----");
return null;
}
}
再编写配置类config
注意我们这里是一环扣一环
ShiroFilterFactoryBean 依赖DefaultWebSecurityManager
DefaultWebSecurityManager 依赖UserRealm
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(securityManager);
return bean;
}
//DefaultWebSecurityManager 需要使用Realm
//@Resource 可以通过 byName 和 byType的方式注入,
// 默认先按 byName的方式进行匹配,如果匹配不到,再按 byType的方式进行匹配
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建Realm对象,需要自定义
@Bean()//让自己写的bean被spring托管
public UserRealm userRealm(){
return new UserRealm();
}
}
这时我们的简单搭建就完成了
编写两个页面测试一下
一个index首页
用了魔板引擎
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>
<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>
</body>
首页跳转到两个页面
add和update
add
update
创建一个controller完成请求的转发
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","helloShiro");
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
}
访问测试
点击添加
没有任何阻拦每个页面都可以访问。
现在我们每个页面都可以访问 要实现访问控制 需要添加内置过滤器
访问控制
过滤的类型:
anno:无需认证即可访问
authc:必须认证了才可以访问
user:必须拥有 记住我功能 才能用
perms :拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
在ShiroFilterFactoryBean中添加内置过滤器
setFilterChainDefinitionMap方法设置一个过滤器的链 参数是一个map
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(securityManager);
//现在我们每个页面都可以访问 要实现访问控制 需要添加内置过滤器
/*
anno:无需认证即可访问
authc:必须认证了才可以访问
user:必须拥有 记住我功能 才能用
perms :拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
// setFilterChainDefinitionMap设置一个过滤器的链
Map<String, String> filterMap =new HashMap<>();
//设置/user/add这个只有认证了(authc)才能访问
filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");
//也支持通配符配置 就是user下面所有的页面都设置认证才能访问的权限
// filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求 没有权限会跳转到登录的页面去认证
bean.setLoginUrl("/toLogin");
return bean;
}
当我们点击页面访问时我们应该跳到一个页面去认证 跟SpringSecurity一样
shiro需要我们自己写一个login页面 设置页面
login页面
<form action="">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p> <input type="submit"></p>
</form>
加上方法
bean.setLoginUrl("/toLogin");
设置路由controller
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
再次访问测试
点击页面跳转
登录一下测试
看后台
说明我们的Realm执行了
这时候没有用户名密码 需要加入认证功能
认证
认证是在Realm里面实现的
我们在controller里面加上判断功能
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello,Shiro");
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据 把用户名密码传进去加密
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
//执行登录方法 如果没有异常说明成功
subject.login(token);
return "index";//成功返回首页
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名错误");
return "login";//回到登录页面
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
}
}
只要点击了登录就会走realm里面的认证方法
重写认证方法
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("----执行了认证-----");
//用户名密码 数据库中取 我们先测试成功与否
String name="root";
String password="111111";
//判断用户名跟密码
UsernamePasswordToken userToken=(UsernamePasswordToken) authenticationToken;
if (!userToken.getUsername().equals(name)){
return null;//抛出异常 UnknownAccountException用户名不存在
}
//密码认证shiro为我们做
//参数三个 1.获取当前用户的认证2.传递密码的对象3.认证名
return new SimpleAuthenticationInfo("",password,"");
}
登录之后页面就可以点击了
如果我们提交一个错误的
会显示错误信息 说明我们认证方法成功了
我们现在只进行了认证还没有授权
我们在shiro的config的ShiroFilterFactoryBean中加入配置
代表了add的页面只有拥有了user:add权限的才能访问
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(securityManager);
//现在我们每个页面都可以访问 要实现访问控制 需要添加内置过滤器
//拦截
// setFilterChainDefinitionMap设置一个过滤器的链
Map<String, String> filterMap =new HashMap<>();
// /user/add 只有拥有了user:add权限的才能访问
//正常情况下没有授权会跳转到未授权页面
filterMap.put("/user/add","perms[user:add]");
//设置update页面只有update权限才能访问
filterMap.put("/user/update","perms[user:update]");
//也支持通配符配置 就是user下面所有的页面都设置认证才能访问的权限
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求 没有权限会跳转到登录的页面去认证
bean.setLoginUrl("/toLogin");
//跳转未授权的页面 跳转到我们指定的页面
bean.setUnauthorizedUrl("/noauth");
return bean;
}
写一个未授权返回的方法
@ResponseBody
@RequestMapping("/noauth")
public String unauthorized(){
return "未经授权无法访问此页面";
}
我们重启访问
访问add就提示 因为我们还没有权限
update正常
授权
我们需要在Relam里面为用户授权
我们应该把权限存在数据库中然后查出来而不是写固定的代码
所以我们为数据库增加一个字段
存储权限的值
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("----执行了授权-----");
//SimpleAuthorizationInfo
//授权的方法
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(
//拿到当前登录的对象 subject就代表当前登录的用户
Subject subject = SecurityUtils.getSubject();
//getPrincipal 取出来的就是
// return new SimpleAuthenticationInfo(user,user.getPassword(),"");返回的这个user
User currentUser = (User)subject.getPrincipal();//拿到user对象
//设置当前用户的权限 currentUser.getPerms()从数据库中拿到权限
info.addStringPermission(currentUser.getPerms());
return info;
}
我们为张三设置了add权限的值 所以可以访问add update页面需要update权限才能访问所以无法访问
无法访问update
执行的流程
当我们点击登录时 会执行Realm里面的认证方法 认证用户
当我们点击页面的时候 会执行授权方法 所以我们要在AuthorizationInfo里面为用户授权
AuthorizationInfo