1.shiro安全框架
1.shiro快速入门:
官网地址: https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/shiro.ini
2.先搭建springboot的运行环境
3.导入shiro-spring依赖
- shiro三大对象:
- Subject : 当前用户
- SecurityManager : 管理所有用户
- Realm : 连接数据
<!-- 整合shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.1</version>
</dependency>
4.Spring整合shiro开始
1.导入依赖
去官网下载最新版 有助于学习进步
<!-- 整合shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.1</version>
</dependency>
- 开始配置
src/main/java/com/kuang/config/ShiroConfig.java
在配置文件中,有三大要素
-
ShiroFilterFactoryBean (过滤器) 3. 最后连接到前端,设置安全管理器
-
DefaultWebSecurityManager 2. 再接管对象,关联realm
-
创建 realm 对象 , 需要自定义 1. 先创建对象
1.先创建对象
我们要创建一个realm类来自定义Realm,再将我们自定义的realm放在配置文件中托管
我们自定义的realm需要继承 AuthorizingRealm
重写里边的两个方法,授权和认证 (AuthorizationInfo授权(AuthenticationInfo认证)
src/main/java/com/kuang/config/UserRealm
public class UserRealm extends AuthorizingRealm { /*授权*/ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("来到了授权===》》》AuthorizationInfo"); return null; } /*认证*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("来到了认证===》》》AuthenticationInfo"); return null; } }
再将自己写的Realm注入到Bean中
src/main/java/com/kuang/config/ShiroConfig.java
//realm @Bean public UserRealm userRealm(){ return new UserRealm(); }
2.再接管对象
src/main/java/com/kuang/config/ShiroConfig.java
这里我们需要思考一个问题:
我们如何将我们自定义的realm交给管理器呢? 直接调用realm方法吗?
不,我们可以使用**@Qualifier(“name”)注解来绑定我们自己写的realm,注解里的name为我们返回realm方法的名称,还有另一种方法,我们@Qualifier(“name”)注解的name可以写Bean里边的name名称/@Bean(name=“userRealm”)**
//DefaultWebSecurityManager
@Bean
public WebSecurityManager getWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm);
return manager;
}
//realm
//@Bean(name="userRealm")
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
3.再连接到前端
下边是我们 src/main/java/com/kuang/config/ShiroConfig.java的总内容
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getWebSecurityManager") WebSecurityManager getWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(getWebSecurityManager);
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager
@Bean
public WebSecurityManager getWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm);
return manager;
}
//realm
//@Bean(name="userRealm")
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
3.shiro里边的内容写好了,开始搭建我们的环境吧。
用controller跳转页面即可,这里省略。
4.环境搭建好之后,我们来实现我们的请求拦截吧!
我们目前拥有的页面
/templates/index.html
/templates/login.html
/templates/user/add.html
/templates/user/update.html
好了,我们想要是实现我们的user里边的添加和更新页面只有认证之后才可以访问,那么怎么办呢?
打开我们shiro的配置类: src/main/java/com/kuang/config/ShiroConfig.java
来比较与上边的配置类多了哪些东西?
只是多了过滤器是吧,我们使用bean来设置我们的过滤请求和登录请求
bean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 添加一个map,key值放路径,value值放权限,可以在源码中进行查看
bean.setLoginUrl("/toLogin"); //当我们没有权限的话,我们会来到这里设置的路径,注意用controller拦截,再跳转。
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getWebSecurityManager") WebSecurityManager getWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(getWebSecurityManager);
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有 记住我 功能才使用
perms:拥有对某个资源的权限才能访问
role:拥有与某个角色才能访问
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/user/*","authc");
/*设置过滤请求*/
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
bean.setLoginUrl("/toLogin");
return bean;
}
//DefaultWebSecurityManager
@Bean
public WebSecurityManager getWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm);
return manager;
}
//realm
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
5.好的,那接下来我们开始进行认证
这里只写后端代码,前端代码可根据后端代码写出。
我们的认证,是在我们的realm中进行认证的,但首先我们需要先接受到我们的用户名和密码,并且将用户名和密码封装在令牌中(token),然后传递给token。
src/main/java/com/kuang/controller/helloController.java
拦截我们form表单提交的请求。
@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中进行认证
src/main/java/com/kuang/config/UserRealm.java
public class UserRealm extends AuthorizingRealm {
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("来到了授权===》》》AuthorizationInfo");
return null;
}
/*认证*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("来到了认证===》》》AuthenticationInfo");
String username = "root";
String passwrod = "123456";
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
/*如果用户名不一致*/
if (!userToken.getUsername().equals(username)){
return null; /*return null会自动抛出异常UnknownAccountException*/
}
/*密码认证不用我们做,shiro做~*/
return new SimpleAuthenticationInfo("",passwrod,"");
}
}
2.shiro整合mybatis
1.导入依赖mybatis 和 MySQL驱动
<!--整合上mybatis-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!--引入mybatis,这是mybatis官方提供的适配springboot的,而不是springboot自己的-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
2. 我们的realm和数据库整合在一起使用
src.main.java.com.kuang.config.UserRealm
我们使用service层,查出数据,来进行验证即可,很简单。
public class UserRealm extends AuthorizingRealm {
@Autowired
UserServiceImpl userService;
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("来到了授权===》》》AuthorizationInfo");
return null;
}
/*认证*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("来到了认证===》》》AuthenticationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.queryUser(userToken.getUsername());
if(user == null){
/*没有该用户*/ /*return null会自动抛出异常UnknownAccountException*/
return null;
}
// /*如果用户名不一致*/
// if (!userToken.getUsername().equals(username)){
// return null; /*return null会自动抛出异常UnknownAccountException*/
// }
/*密码认证不用我们做,shiro做~*/
return new SimpleAuthenticationInfo("",user.getPassword(),"");
}
}
5. 认证搞定了,接下来进行授权
操作
在src.main.java.com.kuang.config.ShiroConfig.java中向过滤器中,添加拦截页面,并添加访问所需权限
在src.main.java.com.kuang.config.UserRealm中进行授权
先添加拦截页面即所需权限,还是添加到map中即可。
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getWebSecurityManager") WebSecurityManager getWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(getWebSecurityManager);
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有 记住我 功能才使用
perms:拥有对某个资源的权限才能访问
role:拥有与某个角色才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/*","authc");
/*设置过滤请求*/
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/toLogin");
/*设置没有权限跳转的页面*/
bean.setUnauthorizedUrl("/unautho");
return bean;
}
在UserRealm中进行授权:
授权的时候,我们需要从数据库中取出权限来,但是我们在认证中查询出来的数据如何在授权当中使用呢??
好的,我们在认证中返回身份信息的时候,我们在第一个参数上,传入我们查询的user,那么我们就可以在授权中使用我们查询出来的user对象了。
注意!我们在授权中如何来得到user这个对象呢?
使用 SecurityUtils 工具类,来获得我们的主体subject,通过subject来得到principal,也就是我们的user,对他进行强制类型转化即可。
注意!我们在数据库中写权限这一栏的时候,格式为:user:add user:update
public class UserRealm extends AuthorizingRealm {
@Autowired
UserServiceImpl userService;
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("来到了授权===》》》AuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
User principal = (User) subject.getPrincipal();
info.addStringPermission(principal.getPerms());
return info;
}
/*认证*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("来到了认证===》》》AuthenticationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User user = userService.queryUser(userToken.getUsername());
if(user == null){
/*没有该用户*/ /*return null会自动抛出异常UnknownAccountException*/
return null;
}
// /*如果用户名不一致*/
// if (!userToken.getUsername().equals(username)){
// return null; /*return null会自动抛出异常UnknownAccountException*/
// }
/*密码认证不用我们做,shiro做~*/
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
6.整合thymeleaf - shiro
导入thymeleaf-shiro整合依赖
<!-- thymeleaf-shiro 整合 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
导入依赖之后,还必须将shiro和thymeleaf交给Bean来管理,之后才可以使用:
@Bean
/*整合ShiroDialect (方言): 用来整合shiro-thymeleaf*/
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
可以导入shiro命名空间
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
>
下来,在前端让有权限的话才可以显示:
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
7.用户已经登录的话,让登录按钮消失
敲重点!
我们在前端使用thymeleaf取session中的值,可以直接使用session再加上点,把值点出来就好了。
可以获得subject主体,通过subject获得session,再向session中存值,在前端页面中判断是否有值。
向session中存值,可以在认证的时候,认证成功之后,我们向session中存入用户
UserRealm.java
/*向session中存入值,在前边判断是否已经登录,已经登陆的话,登录按钮不显示*/
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
在前端判断是否为空
<div th:if="${session.loginUser==null}">
<a th:href="@{thLoginBtn}">登录按钮</a>
</div>