经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
Shiro
来源:cnblogs  作者:江河湖泊  时间:2021/3/29 10:44:48  对本文有异议

上一篇博客介绍SpringBoot:集成SpringSecurity 地址:https://www.cnblogs.com/1693977889zz/p/14584718.html

这篇文章主要介绍一个强大且以用的Java 安全框架 Shiro。Shiro拥有易于理解的API,可以很轻松快捷的搭建应用程序,它没有SpringSecurity的功能强大,但是执行身份验证、授权、密码和会话管理,web集成,缓存它都能做。下面我们来一起学习一下吧:

image

What is Apache Shiro?
Apache Shiro is a powerful and easy to use Java security framework that offers developers an intuitive yet comprehensive solution to authentication, authorization, cryptography, and session management.

In practical terms, it achieves to manage all facets of your application’s security, while keeping out of the way as much as possible. It is built on sound interface-driven design and OO principles, enabling custom behavior wherever you can imagine it. But with sensible defaults for everything, it is as “hands off” as application security can be. At least that’s what we strive for.

image

Mote

image

1.确定环境:JDK 1.8+ and Maven

image

2.The source can be cloned anonymously from Git with this command:(去 github 下载)

git clone https://github.com/apache/shiro.git
git checkout shiro-root-1.7.1 -b shiro-root-1.7.1

3.found under samples/quickstart/src/main/java/Quickstart.java

image

4.模仿 quickstart 搭建项目

image

5.创建 spring-boot-shiro 项目,创建 Maven 模块 hello-shiro

6.导入依赖

<dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.7.1</version>
        </dependency>

        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

7.创建 log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

8.创建 shiro.ini 文件

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# 
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

注意:ini 文件不高亮的话,记得安装插件

image

image

9.导入 QuickStart.java

马上报错:

image

方式一:修改导包:

image

虽然此时方法显示过时了,但是运行起来,并不会报错。

image

方式二:与时俱进,使用 DefaultSecurityManager

image

将如下代码:

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();

替换为:

DefaultSecurityManager securityManager = new DefaultSecurityManager();
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        securityManager.setRealm(iniRealm);

10.Run 测试 Ok!

  • 阅读 QuickStart.java 源码:

Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        //IniSecurityManagerFactory:已废弃
        //Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //SecurityManager securityManager = factory.getInstance();

        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        securityManager.setRealm(iniRealm);

        SecurityUtils.setSecurityManager(securityManager);


        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        //通过SecurityUtils 得到当前用户的对象 Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //这个session不是web、EJB容器,它是Shiro自己的session!!!
        //通过当前用户拿到 session
        Session session = currentUser.getSession();
        //设置
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //判断当前用户是否被认证
        if (!currentUser.isAuthenticated()) {
            //Token:令牌
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);//记住我 功能
            try {
                currentUser.login(token); //执行登录操作
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());//验证用户名
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");//验证密码
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");//锁住用户
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error? 认证异常!
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        //获得当前用户的认证
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role: 获得该用户的角色,该角色拥有什么样的特权
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out! //注销
        currentUser.logout();
        //结束
        System.exit(0);
    }
}

由上面 Quickstart.java 源码得出:

Shiro 三大核心组件

Subject, SecurityManager 和 Realms

(下面的图片来自百度百科)
image

  • SpringBoot 整合 Shiro

1.创建 SpringBoot项目 shiro-springboot

2.导入 Thymeleaf 依赖

<!--Thymeleaf 模板引擎-->
<dependency>
	<groupId>org.thymeleaf</groupId>
	<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

3.编写 index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>首页</h3>

<p th:text="${msg}"></p>
</body>
</html>
```C
4.编写 Controller
```java
package com.zhou.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {

    @RequestMapping({"/","index"})
    public String toIndex(Model model){
        model.addAttribute("msg","Hello,Shiro");
        return "index";
    }
}

5.Run 测试

package com.zhou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ShiroSpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShiroSpringbootApplication.class, args);
    }

}

image

-6.继续,导入 shiro-spring 依赖

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
	<version>1.7.1</version>
</dependency>

7.编写 Config

a. 自定义 UserRealm

package com.zhou.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
//自定义 UserRealm
public class UserRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权:doGetAuthorizationInfo");
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证:doGetAuthenticationInfo");
        return null;
    }
}

b. 定义 ShiroConfig

package com.zhou.config;

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;

@Configuration
public class ShiroConfig {

    //1.创建 realm 对象,需要自定义
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
    //2.DefaultWebSecurityManager
    @Bean(name = "SecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        defaultWebSecurityManager.setRealm(userRealm);
        return defaultWebSecurityManager;
    }
    //3.ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        return shiroFilterFactoryBean;
    }
}
  1. user 包下,创建三个页面
    (SpringSecurity中有个默认的登录,这里没有,自己创建一个登录页 login.html )

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>add</title>
</head>
<body>
添加
</body>
</html>

update.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>update</title>
</head>
<body>
更新
</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form>
    <input type="text" placeholder="用户名" name="username">
    <br><br>
    <input type="password" placeholder="密码" name="password">
    <br><br>
    <input type="submit">
</form>
</body>
</html>

9.Controller

image

@RequestMapping("/add")
public String add(){
	return "/user/add";
}

@RequestMapping("/update")
public String update(){
	return "/user/update";
}

@RequestMapping("/toLogin")
public String toLogin(){
	return "login";
}
  1. index.html 添加代码
<a th:href="@{/add}">add</a>
<a th:href="@{/update}">update</a>

11.Run 访问:http://localhost:8080/

(一切正常,点击add、update 都能点进去)

image

  • Shiro 实现登录拦截

1.添加 Shiro的内置过滤

image

    //3.ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //设置 shiro 的内置过滤器
        /*
        anon:无需认证就可以访问
        authc:必须认证才能访问
        user:必须拥有 记住我 功能才能用
        perms:拥有对某个资源的权限才能访问
        role:拥有某个角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/add","authc");
        filterMap.put("/update","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //设置登录的请求
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        return shiroFilterFactoryBean;
    }

2.Run 访问:http://localhost:8080/ 并点击add、update

发现都被拦截了,并跳到了登录页。

  • Shiro 实现用户认证

1.Controller

    @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);//执行登录方法,如果没有异常就说明OK了
            return "index";
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

2.login.html 添加验证失败信息

<p th:text="${msg}" style="color: red"></p>

3.访问:随便输入用户名和密码,观察控制台

image

查看 AuthenticationInfo,发现它是一个接口:

image

new SimpleAuthenticationInfo 的三个参数:

image

4.伪造一个用户和密码

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证:doGetAuthenticationInfo");
        //用户名、密码 数据库中取(这里先伪造一下)
        String name="zhouzhou";
        String password="1234";
        UsernamePasswordToken userToken=(UsernamePasswordToken)authenticationToken;

        if(!userToken.getUsername().equals(name)){
            return null;//抛出异常 UnknownAccountException
        }
        //密码认证,Shiro来做
        return new SimpleAuthenticationInfo("",password,"");
    }
}

5.Run 测试 OK!

正确的用户名和密码才可以登录成功。

  • Shiro 整合 Mybatis

1.添加依赖

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.3</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.2.5</version>
</dependency>
<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>

注意,maven 资源过滤问题

 <!--在build中配置resources,来防止我们资源导出失败的问题-->
 <resources>
 	<resource>
 		<directory>src/main/resources</directory>
 			<includes>
 				<include>**/*.properties</include>
 				<include>**/*.xml</include>
 			</includes>
 			<filtering>false</filtering>
	 </resource>
 	<resource>
 		<directory>src/main/java</directory>
		 	<includes>
 				<include>**/*.properties</include>
 				<include>**/*.xml</include>
 			</includes>
 			<filtering>false</filtering>
 	</resource>
 </resources>

2.创建数据库

CREATE DATABASE `mybatis`

USE `mybatis`

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `pwd` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

3.插入一些数据,并让 idea 连上数据库

image

3.application.yaml

spring:
  thymeleaf:
    cache: true

  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource #Druid

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 4
    minIdle: 4
    maxActive: 20
    maxWait: 30000
    timeBetweenEvictionRunsMillis: 50000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis:
  type-aliases-package: com.zhou.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

4.创建实体类

package com.zhou.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

5.mapper

UserMapper 接口

package com.zhou.mapper;

import com.zhou.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserMapper {
    public User queryUserByName(String name);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhou.mapper.UserMapper">
    <select id="queryUserByName" parameterType="String" resultType="User">
        select * from mybatis.user where name=#{name}
    </select>
</mapper>

6.service

UserService 接口

package com.zhou.service;

import com.zhou.pojo.User;

public interface UserService {
    public User queryUserByName(String name);
}

UserServiceImpl 实现类

package com.zhou.service;

import com.zhou.mapper.UserMapper;
import com.zhou.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    UserMapper userMapper;
    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

原文链接:http://www.cnblogs.com/1693977889zz/p/14590714.html

 友情链接: NPS