springboot-shiro

Apache Shiro

What is Apache Shiro?

Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。

Apache Shiro的首要目标是易于使用和理解。安全通常很复杂,甚至让人感到很痛苦,但是Shiro却不是这样子的。一个好的安全框架应该屏蔽复杂性,向外暴露简单、直观的API,来简化开发人员实现应用程序安全所花费的时间和精力。

Shiro能做什么呢?

  • 验证用户身份
  • 用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
  • 在非 web 或 EJB 容器的环境下可以任意使用Session API
  • 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
  • 可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
  • 支持单点登录(SSO)功能
  • 支持提供“Remember Me”服务,获取用户关联信息而无需登录

等等——都集成到一个有凝聚力的易于使用的API。

Shiro 致力在所有应用环境下实现上述功能,小到命令行应用程序,大到企业应用中,而且不需要借助第三方框架、容器、应用服务器等。当然 Shiro 的目的是尽量的融入到这样的应用环境中去,但也可以在它们之外的任何环境下开箱即用。

Apache Shiro Features 特性

Apache Shiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架图:

Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:

  • Authentication(认证):用户身份识别,通常被称为用户“登录”
  • Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
  • Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
  • Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。

还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:

  • Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
  • 缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
  • 并发:Apache Shiro 支持多线程应用程序的并发特性。
  • 测试:支持单元测试和集成测试,确保代码和预想的一样安全。
  • “Run As”:这个功能允许用户假设另一个用户的身份(在许可的前提下)。
  • “Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录。

注意: Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro

High-Level Overview 高级概述

在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。

扫描二维码关注公众号,回复: 3985661 查看本文章

  • Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
  • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
  • Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。

我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。

新建一个工程

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--Shiro依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.22</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

实体类的创建  是jpa形式的

package com.shiro.shiro.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import java.io.Serializable;
import java.util.List;

/**
 * @Author zly
 * @Date 2018/11/8 14:19
 */
@Entity
public class SysPermission implements Serializable {
    @Id@GeneratedValue
    private Integer id;//主键.
    private String name;//名称.
    @Column(columnDefinition="enum('menu','button')")
    private String resourceType;//资源类型,[menu|button]
    private String url;//资源路径.
    private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
    private Long parentId; //父编号
    private String parentIds; //父编号列表
    private Boolean available = Boolean.FALSE;
    @ManyToMany
    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
    private List<SysRole> roles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getResourceType() {
        return resourceType;
    }

    public void setResourceType(String resourceType) {
        this.resourceType = resourceType;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public Long getParentId() {
        return parentId;
    }

    public void setParentId(Long parentId) {
        this.parentId = parentId;
    }

    public String getParentIds() {
        return parentIds;
    }

    public void setParentIds(String parentIds) {
        this.parentIds = parentIds;
    }

    public Boolean getAvailable() {
        return available;
    }

    public void setAvailable(Boolean available) {
        this.available = available;
    }

    public List<SysRole> getRoles() {
        return roles;
    }

    public void setRoles(List<SysRole> roles) {
        this.roles = roles;
    }
}

UserInfo.java

package com.shiro.shiro.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import java.io.Serializable;
import java.util.List;

/**
 * @Author zly
 * @Date 2018/11/8 14:04
 */
@Entity
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue
    private Integer uid;
    @Column(unique =true)
    private String username;//帐号
    private String name;//名称(昵称或者真实姓名,不同系统不同定义)
    private String password; //密码;
    private String salt;//加密密码的盐
    private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
    @ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
    @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List<SysRole> roleList;// 一个用户具有多个角色

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public byte getState() {
        return state;
    }

    public void setState(byte state) {
        this.state = state;
    }

    public List<SysRole> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<SysRole> roleList) {
        this.roleList = roleList;
    }

    /**
     * 密码盐.
     * @return
     */
    public String getCredentialsSalt(){
        return this.username+this.salt;
    }
    //重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解
}

SysRole.java

package com.shiro.shiro.entity;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import java.util.List;

/**
 * @Author zly
 * @Date 2018/11/8 14:11
 */
@Entity
public class SysRole {
    @Id@GeneratedValue
    private Integer id; // 编号
    private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
    private String description; // 角色描述,UI界面显示使用
    private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户

    //角色 -- 权限关系:多对多关系;
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
    private List<SysPermission> permissions;

    // 用户 - 角色关系定义;
    @ManyToMany
    @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
    private List<UserInfo> userInfos;// 一个角色对应多个用户

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Boolean getAvailable() {
        return available;
    }

    public void setAvailable(Boolean available) {
        this.available = available;
    }

    public List<SysPermission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<SysPermission> permissions) {
        this.permissions = permissions;
    }

    public List<UserInfo> getUserInfos() {
        return userInfos;
    }

    public void setUserInfos(List<UserInfo> userInfos) {
        this.userInfos = userInfos;
    }
}

配置ShiroConfig

package com.shiro.shiro.config;

import com.shiro.shiro.realm.MyShiroRealm;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author zly
 * @Date 2018/11/8 14:33
 */
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        System.out.println("进入了filter");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
       Map<String, String> linkedHashMap = new LinkedHashMap<>();
       linkedHashMap.put("/static/**","anno");
       linkedHashMap.put("/logout","logout");
       ////<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
       linkedHashMap.put("/**","authc");
      shiroFilterFactoryBean.setLoginUrl("/login");
      //成功之后访问的页面
      shiroFilterFactoryBean.setSuccessUrl("/index");
      shiroFilterFactoryBean.setUnauthorizedUrl("/403");
      shiroFilterFactoryBean.setFilterChainDefinitionMap(linkedHashMap);
      return shiroFilterFactoryBean;
    }
    @Bean
    public MyShiroRealm myshiroRealm(){
        MyShiroRealm realm=new MyShiroRealm();
        return realm;
    }
    @Bean
    public SecurityManager securityManager(){
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        securityManager.setRealm(myshiroRealm());
        return securityManager;
    }
}

自定义realm   MyRealm

package com.shiro.shiro.realm;

import com.shiro.shiro.entity.SysPermission;
import com.shiro.shiro.entity.SysRole;
import com.shiro.shiro.entity.UserInfo;
import com.shiro.shiro.service.UserInfoService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

/**
 * @Author zly
 * @Date 2018/11/8 14:45
 */
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserInfoService userInfoService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        System.out.println("到了权限配置");
        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
        UserInfo userInfo = (UserInfo)principal.getPrimaryPrincipal();
        for (SysRole sysRole : userInfo.getRoleList()) {
            authorizationInfo.addRole(sysRole.getRole());
            for (SysPermission sysPermission : sysRole.getPermissions()) {
                authorizationInfo.addStringPermission(sysPermission.getPermission());
            }
        }

        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        System.out.println("进入了shiro验证");
        String username = (String)token.getPrincipal();
        System.out.println("获取到的签名是:"+token.getPrincipal());
        UserInfo userInfo = userInfoService.findByUserName(username);
        System.out.println("用户信息:"+userInfo);
        if (userInfo==null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo,userInfo.getPassword(),
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),getName());

        return authenticationInfo;
    }
}

dao

package com.shiro.shiro.dao;

import com.shiro.shiro.entity.UserInfo;
import org.springframework.data.repository.CrudRepository;

/**
 * @Author zly
 * @Date 2018/11/8 14:54
 */
public interface UserInfoDao extends CrudRepository<UserInfo,Long>{
            UserInfo findByUsername(String username);
}

service

package com.shiro.shiro.service;

import com.shiro.shiro.entity.UserInfo;

/**
 * @Author zly
 * @Date 2018/11/8 14:59
 */
public interface UserInfoService {
    UserInfo findByUserName(String username);
}

Impl

package com.shiro.shiro.service.impl;

import com.shiro.shiro.dao.UserInfoDao;
import com.shiro.shiro.entity.UserInfo;
import com.shiro.shiro.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author zly
 * @Date 2018/11/8 15:00
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {
    @Autowired
    private UserInfoDao userInfoDao;
    @Override
    public UserInfo findByUserName(String username) {

        return userInfoDao.findByUsername(username);
    }
}

UserInfoController

package com.shiro.shiro.controller;

import com.shiro.shiro.service.UserInfoService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @Author zly
 * @Date 2018/11/8 15:20
 */
@Controller
@RequestMapping("/userInfo")
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;

    @RequestMapping("/userList")
    @RequiresPermissions("userInfo:view")//权限管理
    public String userInfo(){
    return "userInfo";
    }
    @RequestMapping("/userAdd")
    @RequiresPermissions("userInfo:add")
    public String addUser(){
        return "userInfoAdd";
    }
    @RequestMapping("userDel")
    @RequiresPermissions("userInfo:del")
    public String del(){
        return "userInfoDel";
    }
}

HomeController

package com.shiro.shiro.controller;

import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @Author zly
 * @Date 2018/11/8 15:34
 */@Controller
public class HomeContorller {
     @RequestMapping({"/","/index"})
    public String index(){
         return "index";
     }
     @RequestMapping("/login")
    public String login(HttpServletRequest request, Map<String,Object> map){
         System.out.println("进入了登录操作");
         String faile = (String)request.getAttribute("shiroLoginFaile");
         System.out.println("faile"+faile);
         String msg="";
         if (faile!=null){
             if(UnknownAccountException.class.getTypeName().equals(faile)){
                 System.out.println("账号不存在");
                 msg="账号不存在";
             }else if (IncorrectCredentialsException.class.getTypeName().equals(faile)){
                 System.out.println("密码错误");
                 msg="密码错误";
             }else if ("KaptchaValidateFailed".equals(faile)){
                 System.out.println("验证码错误");
                 msg="验证码错误";
             }else {
                 msg=faile;
                 System.out.println(faile);
             }


         }
         map.put("msg", msg);
         // 此方法不处理登录成功,由shiro进行处理
         return "/login";
     }
    @RequestMapping("/403")
    public String unauthorizedRole(){
        System.out.println("------没有权限-------");
        return "403";
    }
}

页面贴一下登录界面

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
错误信息:<h4 th:text="${msg}"></h4>
<form action="" method="post">
    <p>账号:<input type="text" name="username" value="admin"/></p>
    <p>密码:<input type="text" name="password" value="123456"/></p>
    <p><input type="submit" value="登录"/></p>
</form>
</body>
</html>

yml配置文件

spring:
  datasource:
    username: root
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://99.48.232.39:3306/boot?characterEncoding=utf8
    password: 1qaz@WSX
    hikari:
                      minimum-idle: 5
                      maximum-pool-size: 15
                      auto-commit: true
                      idle-timeout: 30000
                      max-lifetime: 1800000
                      connection-timeout: 30000
  jpa:
    database: mysql
    show-sql: true
    hibernate:
        ddl-auto: update
        naming:
          strategy: org.hibernate.cfg.DefaultComponentSafeNamingStrategy
    properties:
        hibernate:
          dialect: org.hibernate.dialect.MySQL5Dialect

  thymeleaf:
    cache: false
    mode: LEGACYHTML5

最后看一下项目目录

启动项目登录访问

猜你喜欢

转载自www.cnblogs.com/blackCatFish/p/9929787.html