【无标题】

视频来源 编程不良人Shiro

1. 权限管理

1.1 什么是权限管理

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户只能访问自己被授权的资源。

权限管理包括身份认证和授权两部分,简称认证授权。对于需要访问系统资源的用户先进行身份认证,认证通过后再看该用户具有的权限是否能访问该资源。

1.2 身份认证 authentication

就是判断一个用户是否为合法用户的过程,最简单的身份认证就是用户在系统中输入用户名和密码,是否和系统中该用户的信息一致,来判断身分是否正确。

1.3 授权 authorization

授权,即访问控制,控制用户能够访问哪些资源。

2. Shiro

img

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

Shiro是一款功能强大的且简单易用的Java安全框架,它执行身份认证,授权,加密和会话管理。使用Shiro易于理解的API,你可以快速轻松上手保护任何应用程序,从最小的移动应用到最大的web和企业应用。

总结:shiro是apache下的一款开源框架,它将软件系统的安全认证功能抽离出来,实现了身份认证、授权、加密、会话管理等功能,组成了一个通用的安全框架。

img

  • 主体 (org.apache.shiro.subject.Subject)
    当前与软件交互的实体(用户,第三方服务,cron 作业等)的特定于安全性的“视图”。
  • SecurityManager (org.apache.shiro.mgt.SecurityManager)
    如上所述,SecurityManager是 Shiro 架构的核心。它主要是一个“伞”对象,用于协调其托管组件以确保它们能够顺利协同工作。它还 ManagementShiro 对每个应用程序用户的视图,因此它知道如何对每个用户执行安全性操作。
  • 认证者 (org.apache.shiro.authc.Authenticator)
    Authenticator是负责执行用户的身份验证(登录)并对其作出反应的组件。当用户尝试登录时,该逻辑由Authenticator执行。 Authenticator知道如何与一个或多个存储相关用户/帐户信息的Realms进行协调。从这些Realms获得的数据用于验证用户的身份,以确保用户确实是他们所说的真实身份。
  • 认证策略 (org.apache.shiro.authc.pam.AuthenticationStrategy)
    如果配置了多个Realm,则AuthenticationStrategy将协调领域以确定身份验证尝试成功或失败的条件(例如,如果一个领域成功但其他领域失败,则该尝试成功吗?是否所有领域都必须成功?第一?)。
  • 授权人 (org.apache.shiro.authz.Authorizer)
    Authorizer是负责确定应用程序中用户访问控制的组件。它是最终表明是否允许用户做某事的机制。与Authenticator一样,Authorizer也知道如何与多个后端数据源进行协调以访问角色和权限信息。 Authorizer使用此信息来确定是否允许用户执行给定的动作。
  • SessionManager (org.apache.shiro.session.mgt.SessionManager)
    SessionManager知道如何创建和 Management 用户Session的生命周期,以便为所有环境中的用户提供可靠的会话体验。这是安全框架领域中的一项独特功能-Shiro 能够在任何环境中本地 Management 用户会话,即使没有 Web/Servlet 或 EJB 容器也可以。默认情况下,Shiro 将使用现有的会话机制(例如 Servlet 容器)(如果可用),但是如果没有这种机制(例如在独立应用程序或非 Web 环境中),它将使用其内置的企业会话 Management 来提供相同的编程经验。 SessionDAO的存在是为了允许使用任何数据源来保留会话。
  • SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO)
    SessionDAO代表SessionManager执行Session持久性(CRUD)操作。这允许将任何数据存储插入会话 Management 基础结构。
  • CacheManager (org.apache.shiro.cache.CacheManager)
    CacheManager创建和 Management 其他 Shiro 组件使用的Cache实例生命周期。由于 Shiro 可以访问许多后端数据源以进行身份验证,授权和会话 Management,因此缓存一直是框架中的一流架构功能,可以在使用这些数据源时提高性能。可以将任何现代的开源和/或企业缓存产品插入 Shiro,以提供快速有效的用户体验。
  • 密码学 (org.apache.shiro.crypto.*)
    密码术是企业安全框架的自然补充。 Shiro 的crypto软件包包含易于使用和理解的密码学密码,哈希(又名摘要)和不同编解码器实现的表示形式。该软件包中的所有类都经过精心设计,以使其易于使用和理解。使用 Java 的本机加密技术支持的任何人都知道,驯服它可能是具有挑战性的动物。 Shiro 的加密 API 简化了复杂的 Java 机制,并使加密技术易于普通凡人使用。
  • Realm (org.apache.shiro.realm.Realm)
    如上所述,领域充当 Shiro 与应用程序安全数据之间的“bridge 梁”或“连接器”。当 true 需要与安全性相关的数据(例如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro 会从一个或多个为应用程序配置的领域中查找许多此类内容。您可以根据需要配置多个Realms(通常每个数据源一个),并且 Shiro 会根据需要与它们进行协调,以进行身份验证和授权。

3. 第一个用户认证程序

认证大体流程

在这里插入图片描述

使用springboot搭建
1. 先创建一个安全管理器 DefaultSecurityManager
2. 定义好realm(就是ini文件信息)
3. 给安全管理器设置一个realm
4. 给全局工具类(SecurityUtils)设置全局管理器(DefaultSecurityManager)
5. 通过全局工具类获取一个主体(Subject)
6. 封装安全令牌(UsernamePasswordToken),构造传入用户名、密码等信息
7. 主体验证令牌

编写shiro.ini

在shiro.ini中存储用户信息

[users]
lyk=123456
ljj=123456

main.java

public static void main(String[] args) {
        //1:创建安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2:给安全管理器设置 Realm
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        securityManager.setRealm(iniRealm);
        //3:给全局工具类设置全局管理器
        SecurityUtils.setSecurityManager(securityManager);
        //3:获取主体对象
        Subject subject = SecurityUtils.getSubject();
        //5:创建安全令牌
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "123456");
        usernamePasswordToken.setRememberMe(true);
        try {
            subject.login(usernamePasswordToken);
        } catch (UnknownAccountException e) {
            System.out.println("账户错误:" + e);
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误:" + e);
        }
    }
用户名错误会报错误 UnknownAccountException
密码错误会报错误 IncorrectCredentialsException

4. 认证源码

最终执行用户名比较是在SimpleAccountRealm

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    UsernamePasswordToken upToken = (UsernamePasswordToken)token;
    SimpleAccount account = this.getUser(upToken.getUsername());
    if (account != null) {
        if (account.isLocked()) {
            throw new LockedAccountException("Account [" + account + "] is locked.");
        }

        if (account.isCredentialsExpired()) {
            String msg = "The credentials for account [" + account + "] are expired";
            throw new ExpiredCredentialsException(msg);
        }
    }

最终执行密码校验是在AuthorizingRealm

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
    CredentialsMatcher cm = this.getCredentialsMatcher();
    if (cm != null) {
        if (!cm.doCredentialsMatch(token, info)) {
            String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
            throw new IncorrectCredentialsException(msg);
        }
    } else {
        throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication.  If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
    }
}

分析完源码,我们发现我们可以自定义Realm,覆盖认证和授权的操作

package com.kang.realm;

public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
	
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

5. MD5加密

MD5 算法简介:

1)作用:一般用来加密、签名。
2)特点:MD5 算法不可逆,内容相同,无论执行多少次,结果始终一样。
3)结果:始终是一个 16进制,32位长度的字符串。
//        md5
Md5Hash md5Hash = new Md5Hash("123123");
System.out.println(md5Hash.toHex());
//        md5+盐
md5Hash = new Md5Hash("123123","avcdrt");
System.out.println(md5Hash.toHex());
//        md5+盐+hash散列
md5Hash = new Md5Hash("123123","avcdrt",1024);
System.out.println(md5Hash.toHex());

使用方法

在自定义Realm中存放加密数据

 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        System.out.println(principal);
        if ("张三".equals(principal)) {
            return new SimpleAuthenticationInfo("张三",
                    "5194528bd63abca4bf9d09885bb425b7",
                    ByteSource.Util.bytes("swfa"),
                    this.getName());
        }
        return null;
    }

然后设置hash算法,更改Realm的默认加密处理

HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1024);
//放入hash算法规则
myMd5Realm.setCredentialsMatcher(matcher);

自动生成Salt工具类

package com.kang.shiro;

import java.util.Random;

public class CodeUtil {
    private final static String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-*/+";

    private final static String num = "0123456789";

    /**
     * 生成盐
     *
     * @return
     */
    public static String salt() {
        String uuid = new String();
        for (int i = 0; i < 6; i++) {
            char ch = str.charAt(new Random().nextInt(str.length()));
            uuid += ch;
        }
        return uuid;
    }

    public static void main(String[] args) {
        System.out.println(salt());
    }
}

6. 授权

先认证,再授权

在这里插入图片描述

授权方式

基于角色的访问控制

subject.hasRole("admin"){
    //admin用户可以访问的页面
}

基于资源的访问控制

if (subject.isPermitted("admin:update:1")){
    //admin修改1号用户的权限
}

权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具体有什么操作,可以使用*进行通配。

对于用户的创建权限 :        user:create 或者 user:create:*
用户修改实例001的权限:      user:update:001
用户修改实例001的所有权限:   user:*:001

Shiro给主体授权的操作

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String primaryPrincipal = (String) principals.getPrimaryPrincipal();
    HashSet<String> hashSet = new HashSet<>();
    //给主体赋角色
    hashSet.add("admin");
    hashSet.add("user");
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(hashSet);
    //赋资源操作
    simpleAuthorizationInfo.addStringPermission("user:*:001");
    return simpleAuthorizationInfo;
}

Shiro中验证身份方式

编程式

subject.hasRole("admin"){
    //admin用户可以访问的页面
}
if (subject.isPermitted("admin:update:1")){
    //admin修改1号用户的权限
}

注解式

@RequiresRoles()  基于角色
@RequiresPermissions() 基于资源
//这两个有一个就行了
public void a(){
    //有权限才会进入
}

标签式

<shiro:hasPermission name="user:add:*">
    内容
</shiro:hasPermission>
<shiro:hasRole name="admin">
    内容
</shiro:hasRole>

7. SpringBoot整合Shiro

增加maven依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

yaml配置

 mvc:
    view:
      prefix: /
      suffix: .jsp

创建ShiroConfigjava.java

只需要在配置类中注入三个核心对象

  • ShiroFilterFactoryBean

Shiro各类滤器的指定功能

在这里插入图片描述

  • DefaultWebSecurityManager
  • Realm
package com.kang.config;

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }

    @Bean
    public Realm getRealm(){
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }
}

自定义Realm

MyReam.java

/自定义Realm
    public class MyRealm extends AuthorizingRealm {
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;
        }

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            return null;
        }
    }

login.jsp

<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>登陆页面</title>
    </head>
    <body>
        <h1>我的登陆页面</h1>
        <form action="" method="post">
            用户名:<input type="text" name="username"><br>
            密码:<input type="text" name="password"><br>
            <input type="submit" value="登陆">
        </form>
    </body>
</html>

编写实体类entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Permission implements Serializable {
    private String id;
    private String name;
    private String url;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role implements Serializable {
    private String id;
    private String name;
    private List<Permission> permissions;
}


@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
    private String salt;
    private List<Role> roles;
}

运行查看结果:

在这里插入图片描述

认证和退出

修改Realm

//自定义Realm
public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        if ("lyk".equals(principal)) {
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, "123123", this.getName());
            return authenticationInfo;
        }
        return null;
    }
}

写Controller方法

UserController.java

@Controller
@RequestMapping("/user")
public class UserController {
    @PostMapping(value = "/login")
    public String login(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        try {
            subject.login(usernamePasswordToken);
            return "redirect:/index.jsp";
        } catch (UnknownAccountException e) {
            System.out.println("账号错误");
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误");
        }
        return "redirect:/login.jsp";
    }
}

退出登录

编写Controller

UserController.java

@Controller
@RequestMapping("/user")
public class UserController {
    @GetMapping(value = "/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            subject.logout();
        }
        return "redirect:/login.jsp";
    }
}

index.jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>菜单页面</title>
</head>
<body>
<h1>菜单页面</h1>
<ul>
    <li><a href="">用户管理</a></li>
    <li><a href="">订单管理</a></li>
    <li><a href="">商品管理</a></li>
    <li><a href="">物流管理</a></li>
    <li><a href="${pageContext.request.contextPath}/user/logout">退出登陆</a></li>
</ul>
</body>

mybatis连接数据库

数据库设计

img

sql文件

/*
SQLyog Ultimate v10.00 Beta1
MySQL - 5.5.62 : Database - bilibili
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`bilibili` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;

USE `bilibili`;

/*Table structure for table `permission` */

DROP TABLE IF EXISTS `permission`;

CREATE TABLE `permission` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT '' COMMENT '名称',
  `url` varchar(128) DEFAULT '' COMMENT '接口路径',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `permission` */

insert  into `permission`(`id`,`name`,`url`) values (1,'user:*:*',''),(2,'product:*:01',''),(3,'order:*:*','');

/*Table structure for table `role` */

DROP TABLE IF EXISTS `role`;

CREATE TABLE `role` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT '' COMMENT '名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `role` */

insert  into `role`(`id`,`name`) values (1,'admin'),(2,'user'),(3,'product');

/*Table structure for table `role_permission` */

DROP TABLE IF EXISTS `role_permission`;

CREATE TABLE `role_permission` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) DEFAULT NULL,
  `permission_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

/*Data for the table `role_permission` */

insert  into `role_permission`(`id`,`role_id`,`permission_id`) values (1,1,1),(2,1,2),(3,2,1),(4,3,2),(5,1,3);

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` bigint(10) NOT NULL AUTO_INCREMENT,
  `username` varchar(155) COLLATE utf8_unicode_ci DEFAULT NULL,
  `password` varchar(155) COLLATE utf8_unicode_ci DEFAULT NULL,
  `salt` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

/*Data for the table `user` */

insert  into `user`(`id`,`username`,`password`,`salt`) values (1,'admin','032fc908968ed4de4d917d15725972ed','\'NUH'),(2,'zhangsan','9181fa5aed0d237eb9b3b57952709d2e','ZPP/');

/*Table structure for table `user_role` */

DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) DEFAULT NULL,
  `user_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

/*Data for the table `user_role` */

insert  into `user_role`(`id`,`role_id`,`user_id`) values (1,1,1),(2,2,2),(3,3,2),(4,2,1);

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

增加maven配置

<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>
</dependency>
<properties>  
    <mysql.version>5.5.62</mysql.version>
</properties>

applicationContext.yaml

修改SpringBoot配置文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/bilibili?useUnicode=true&characterEncodind=utf8&useSSL=true&serverTimezone=GMT
    username: root
    password: 123123
    driver-class-name: com.mysql.jdbc.Driver
mybatis:
  type-aliases-package: com.kang.entity
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

基于MD5+Salt的注册功能

adduser.jsp

添加用户页面

<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>添加用户</title>
</head>
<body>
<h1>添加用户</h1>
<form action="${pageContext.request.contextPath}/user/adduser" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="text" name="password"><br>
    <input type="submit" value="登陆">
</form>
</body>
</html>

UserDao.java

public interface UserDao {
    int addUser(User user);
}

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.kang.dao.UserDao">
    <insert id="addUser">
        insert into user (username, password, salt)
        values (#{username}, #{password}, #{salt})
    </insert>
</mapper>

UserService.java

@Service("userService")
public class UserServiceImpl implements UserService{
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;
    @Override
    public int addUser(User user) {
        String salt = SaltUtils.getSalt(4);
        user.setSalt(salt);
        Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
        user.setPassword(md5Hash.toHex());
        return userDao.addUser(user);
    }
}

UserController

@PostMapping("/adduser")
public String addUser(User user){
    try {
        userService.addUser(user);
        return "redirect:/login.jsp";
    }catch (Exception e){
        e.printStackTrace();
        return "redirect:/adduser.jsp";
    }
}

基于MD5+Salt的登录功能

UserDao.java

User findUserByName(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.kang.dao.UserDao">
    <select id="findUserByName" resultType="user">
        select *
        from user
        where username = #{username}
    </select>
    
</mapper>

…省略Service层

UserController

@PostMapping("/login")
public String login(String username, String password){

    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
        subject.login(token);
        return "redirect:/index.jsp";
    }catch (UnknownAccountException e){
        System.out.println(e.getMessage());
        System.out.println("账号不存在");
    }catch (IncorrectCredentialsException e){
        e.printStackTrace();
        System.out.println("密码错误");
    }
}

MyReam.java

修改Realm的doGetAuthenticationInfo

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    String principal = (String) authenticationToken.getPrincipal();
    UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
    User user = userService.findUserByName(principal);
    if (!StringUtils.isEmpty(user)) {
        return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), new MyByteSource(user.getSalt()), this.getName());
    }
    return null;
}

这里注意一个细节,MyReam这个类是没有注入到容器里面的,所以不可以直接注入UserService这个类

这里使用一个工具类用来获取工厂里的对象

ApplicationContextUtil.java

package com.kang.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextUtils implements ApplicationContextAware {
    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    //直接调用这个方法就可以获取工厂中的对象
    public static Object getBean(String beanName){
        return context.getBean(beanName);
    }
}

最后一步,也是最重要的一步

ShiroConfig.java中增加对Realm的配置

@Bean
public Realm getRealm(){
    MyRealm myRealm = new MyRealm();
    
    HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    matcher.setHashIterations(1024);
    matcher.setHashAlgorithmName("md5");
    myRealm.setCredentialsMatcher(matcher);
    
    return myRealm;

授权功能

基于角色授权

编写dao层
//根据用户名查询所有的角色
User findRolesByName(String username);
<?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.kang.dao.UserDao">
    
    <resultMap id="userMap" type="User">
        <id column="uid" property="id"/>
        <result column="username" property="username"/>
        <collection property="roles" javaType="list" ofType="Role">
            <id column="rid" property="id"/>
            <result column="rname" property="name"/>
        </collection>
    </resultMap>
    <select id="findRolesByName" resultMap="userMap">
        SELECT u.`id`   uid,
               u.`username`,
               r.`id`   rid,
               r.`name` rname
        FROM `role` r
                 LEFT JOIN `user_role` ur
                           ON r.`id` = ur.role_id
                 LEFT JOIN `user` u
                           ON ur.user_id = u.`id`
        WHERE u.`username` = #{username}
    </select>
</mapper>
编写业务层

这里就不写了~~~

编写MyRealm
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
    UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
    //从数据库中根据用户名查找到的角色信息
    //并把查询道德所有角色信息增加到主体上
    User user = userService.findRolesByName(primaryPrincipal);
    if (!CollectionUtils.isEmpty(user.getRoles())) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        user.getRoles().forEach(role -> {
            simpleAuthorizationInfo.addRole(role.getName());
        });
        return simpleAuthorizationInfo;
    }
    return null;
}

基于资源权限授权

编写dao层
//根据角色id查询权限信息的方法
List<Permission> findPermissionsById(String id);
<?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.kang.dao.UserDao">
    
    <select id="findPermissionsById" parameterType="String" resultType="Permission">
        SELECT p.id, p.name
        FROM role r
                 LEFT JOIN `role_permission` rp ON r.`id` = rp.role_id
                 LEFT JOIN permission p ON p.id = rp.permission_id
        WHERE r.`id` = #{id}
    </select>
</mapper>
Service层

这一层就省略不写了~~~

编写MyRealm
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
    UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
    User user = userService.findRolesByName(primaryPrincipal);
    if (!CollectionUtils.isEmpty(user.getRoles())) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        user.getRoles().forEach(role -> {
            simpleAuthorizationInfo.addRole(role.getName());
            
            //一个用户可能存在多个角色
            //然后基于每一个角色,查找这个角色所拥有的权限信息,赋值用户主体
            List<Permission> permissions = userService.findPermissionsById(role.getId());
            if (!CollectionUtils.isEmpty(permissions)){
                permissions.forEach(permission -> {
                    simpleAuthorizationInfo.addStringPermission(permission.getName());
                });
            }
        });
        return simpleAuthorizationInfo;
    }
    return null;
}

8. 整合Redis

  • 开启Redis服务

  • 去appicationContext中去配置 redsi

Spring:
  redis:
    port: 6379
    host: 192.168.237.135
    database: 0

编写RedisCacheManager

public class RedisCacheManager implements CacheManager {
    /**
     * 
     * @param cachename 认证或者授权缓存的统一名称
     */
    @Override
    public <K, V> Cache<K, V> getCache(String cachename) throws CacheException {
        return new RedisCache<>(cachename);
    }
}

编写Chcahe

//自定义redis缓存的实现
public class RedisCache<k, v> implements Cache<k, v> {

    private String cacheName;

    public RedisCache() {
    }

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public v get(k k) throws CacheException {
        System.out.println("get key " + k);
        return (v) getRedisTemplate().opsForHash().get(cacheName, k.toString());
    }

    @Override
    public v put(k k, v v) throws CacheException {
        System.out.println("put key" + k);
        System.out.println("put value" + v);
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.opsForHash().put(cacheName, k.toString(), v);
        return null;
    }

    @Override
    public v remove(k k) throws CacheException {
        return (v) getRedisTemplate().opsForHash().delete(cacheName, k.toString());
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().opsForHash().delete(cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(cacheName).intValue();
    }

    @Override
    public Set<k> keys() {
        return getRedisTemplate().opsForHash().keys(cacheName);
    }

    @Override
    public Collection<v> values() {
        return getRedisTemplate().opsForHash().values(cacheName);
    }

    private RedisTemplate getRedisTemplate() {
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

在ShiroConfig中开启缓存管理

    @Bean
    public Realm getRealm(){
        MyRealm myRealm = new MyRealm();
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashIterations(1024);
        matcher.setHashAlgorithmName("md5");
        myRealm.setCredentialsMatcher(matcher);

//        开启缓存管理
        myRealm.setCacheManager(new RedisCacheManager());
        myRealm.setCachingEnabled(true);
        myRealm.setAuthenticationCacheName("authenticationCache");
        myRealm.setAuthenticationCachingEnabled(true);
        myRealm.setAuthorizationCacheName("authorizationCache");
        myRealm.setAuthorizationCachingEnabled(true);
        return myRealm;
    }

9. 登录验证码

ImageUtil.java

/**
 * @title 验证码工具类
 * @author boat
 */
public class ImageUtil {

    private static int width = 90;// 定义图片的width
    private static int height = 30;// 定义图片的height

    //5个验证码一组
   /* private static int codeCount = 5;// 定义图片上显示验证码的个数
    private static int xx = 13;*/

    //4个验证码一组
    private static int codeCount = 4;// 定义图片上显示验证码的个数
    private static int xx = 15;

    private static int codeY = 23;
    private static int fontHeight = 22;

    private static char[] codeSequence = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t', 'u','v','w','x','y','z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

    /**
     * 生成一个map集合
     * code为生成的验证码
     * codePic为生成的验证码BufferedImage对象
     *
     * @return
     */
    public static Map<String, Object> generateCodeAndPic() {
        // 定义图像buffer
        BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // Graphics2D gd = buffImg.createGraphics();
        // Graphics2D gd = (Graphics2D) buffImg.getGraphics();
        Graphics gd = buffImg.getGraphics();

        // 创建一个随机数生成器类
        Random random = new Random();

        // 将图像填充为白色
        gd.setColor(Color.WHITE);
        gd.fillRect(0, 0, width, height);

        // 创建字体,字体的大小应该根据图片的高度来定。
        Font font = new Font("Arial", Font.BOLD, fontHeight);

        // 设置字体。
        gd.setFont(font);

        // 画边框。
        //gd.setColor(Color.LIGHT_GRAY);
        gd.setColor(getRandomColor());
        gd.drawRect(0, 0, width - 1, height - 1);

        // 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
        gd.setColor(getRandomColor());

        for (int i = 0; i < 30; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            gd.drawLine(x, y, x + xl, y + yl);
        }

        // randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
        StringBuffer randomCode = new StringBuffer();

        // 随机产生codeCount数字的验证码。
        for (int i = 0; i < codeCount; i++) {

            // 得到随机产生的验证码数字。
            String code = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);

            // 用随机产生的颜色将验证码绘制到图像中。
            gd.setColor(getRandomColor());

            gd.drawString(code, (i + 1) * xx, codeY);

            // 将产生的四个随机数组合在一起。
            randomCode.append(code);
        }
        Map<String, Object> map = new HashMap<String, Object>();
        //存放验证码
        map.put("code", randomCode);
        //存放生成的验证码BufferedImage对象
        map.put("codePic", buffImg);
        return map;
    }

    /**
     * 随机取色
     */
    public static Color getRandomColor() {
        Random ran = new Random();
        int red = ran.nextInt(256);
        int green = ran.nextInt(256);
        int blue = ran.nextInt(256);
        Color color = new Color(red,green,blue);
        return color;
    }

    // 获取验证码
    public static Map<String,Object> getCheckCodePic(){
        return ImageUtil.generateCodeAndPic();
    }

    public static void main(String[] args) throws Exception {
        //创建文件输出流对象
        OutputStream out = new FileOutputStream("d://"+System.currentTimeMillis()+".jpg");
        Map<String,Object> map = ImageUtil.generateCodeAndPic();
        ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", out);
        System.out.println("验证码的值为:"+map.get("code"));
    }

}

然后写一个Controller方法调用

@GetMapping("verify")
@ResponseBody
public String vertify(HttpServletResponse response, HttpSession session) throws IOException {
    Map<String, Object> checkCodePic = ImageUtil.getCheckCodePic();
    //获取一个输出流
    ServletOutputStream outputStream = response.getOutputStream();
    //获取验证码信息,并存入到Session中,以便验证
    String stringCode = checkCodePic.get("code").toString();
    session.setAttribute("code", stringCode);
   	//流传输一个图片验证码
    ImageIO.write((RenderedImage) checkCodePic.get("codePic"), "jpeg", outputStream);
    return null;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值