Shiro整合Mybatis实现用户认证和授权

目录

前言

一、Shiro介绍

1、Shiro基本功能图

2、Shiro工作原理

二、Shiro实现认证和授权

1、先导入相关的依赖

2、属性配置文件

3、dao层配置 

4、实体类配置 

5、Service层配置 

6、Controller层配置

7、配置类 

8、运行结果


前言

Apache Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证、授权、加密和会话管理。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。本文使用Shiro整合Mybatis,利用Druid数据库连接池,实现用户认证和授权,并利用Swagger生成API开发文档。

一、Shiro介绍

可以根据官网或者github快速入门Shiro安全框架

1、Shiro基本功能图

  • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

2、Shiro工作原理

Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;它是一个比较抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与其他组件进行交互,如果学习过 SpringMVC框架,你就可以把它看成前端控制器( DispatcherServlet );

Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

二、Shiro实现认证和授权

1、先导入相关的依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--shiro整合spring的包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.9.0</version>
        </dependency>

        <!--thymeleaf模板-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.15.RELEASE</version>
        </dependency>
       <!--mysql连接驱动、Druid数据源、log4j、mybatis、swagger3.0-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
    </dependencies>

2、属性配置文件

主要是对数据库的连接、数据库连接池Druid、Mybatis、log4j进行配置

application.yaml:

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
  type-aliases-package: com.study.pojo
  mapper-locations: classpath:mapper/*.xml

log4j.properties: 

#应用于控制台
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

3、dao层配置 

UserMapper.java:

package com.study.mapper;


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

@Repository
@Mapper
public interface UserMapper {
    //通过名字查询用户
    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.study.mapper.UserMapper">
    <select id="queryUserByName" resultType="user">
        select *
        from mybatis.user
        where username=#{username}
    </select>
</mapper>

4、实体类配置 

User.java:

package com.study.pojo;

import java.util.Collection;

public class User {
    private Integer userid;
    private String username;
    private String password;
    private String perms;

    public User() {

    }

    public User(Integer userid, String username, String password,String perms) {
        this.userid = userid;
        this.username = username;
        this.password = password;
        this.perms=perms;
    }

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

    public String getPerms() {
        return perms;
    }

    public void setPerms(String perms) {
        this.perms = perms;
    }

    @Override
    public String toString() {
        return "User{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", perms='" + perms + '\'' +
                '}';
    }
}

5、Service层配置 

UserService.java:

package com.study.service;

import com.study.pojo.User;

public interface UserService {
    //通过名字查询用户
    User queryUserByName(String name);
}

UserServiceImpl.java:

package com.study.service;

import com.study.mapper.UserMapper;
import com.study.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);
    }

}

6、Controller层配置

package com.study.controller;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
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";
    }

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

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

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


}

7、配置类 

配置类包含对Druid数据库连接池的配置,Shiro登录拦截,Shiro授权及认证的配置,Swagger配置

DruidConfig:

package com.study.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;

@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

    //后台监控功能
    //因为SpringBoot内置Servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        //后台需要有人登录,账号密码配置
        HashMap<String, String> initParameters = new HashMap<>();
        //增加配置
        initParameters.put("loginUsername", "admin");//登录key是固定的loginUsername
        initParameters.put("loginPassword", "123456");
        //允许谁可以访问
        initParameters.put("allow", "");
        //禁止谁能访问 initParameters.put("deny","192.168.1.1");
        bean.setInitParameters(initParameters);//设置初始化参数
        return bean;
    }
    //配置一个web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        //可以过滤哪些请求呢?
        HashMap<String, String> initParameters = new HashMap<>();
        bean.setInitParameters(initParameters);
        //这些东西不进行统计
        initParameters.put("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return bean;
    }
}

ShiroConfig: 

package com.study.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;

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

@Configuration
public class ShiroConfig  {
    //第三步:ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的内置过滤器
        /*
         * anon:无需认证就可以访问
         * authc:必须认证了才可以访问
         * user:必须拥有记住我功能才能用
         * perms:拥有对某个资源的权限才能访问
         * role:拥有某个角色权限才能访问
         * */
        //拦截
        Map<String, String> filterMap=new LinkedHashMap<>();
        //授权,正常的情况下,没有授权会跳到未授权的页面
        filterMap.put("/user/character","perms[user:character]");
        filterMap.put("/user/picture","perms[user:picture]");
        //filterMap.put("/user/add","authc");
        //filterMap.put("/user/update","authc");
        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);
        //设置登录的请求
        bean.setLoginUrl("/toLogin");
        return bean;
    }
    //第二步:DefaultWebSecurityManager
    @Bean(name ="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm对象
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    //第一步:创建realm对象,需要自定义类
    @Bean //将自定义的类放入ShiroConfig,并被Spring托管
    public UserRealm userRealm(){
        return new UserRealm();
    }

}

UserRealm: 

package com.study.config;

import com.study.pojo.User;
import com.study.service.UserServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserServiceImpl userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //拿到当前登录的这个对象
        Subject subject = SecurityUtils.getSubject();
        User currentUsers = (User) subject.getPrincipal();//拿到User对象
        //设置当前用户的权限
        if (currentUsers.getPerms()!=null) {
            info.addStringPermission(currentUsers.getPerms());
            return info;
        }
        else {
            return null;
        }
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //用户名,密码,从数据库中取
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        User user = userService.queryUserByName(userToken.getUsername());
        if (user == null) { //不存在这个用户
            return null;
        }
        //密码认证,shiro去做
        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }
}

SwaggerConfig: 

package com.study.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;


@Configuration
@EnableOpenApi
@EnableWebMvc
public class SwaggerConfig {
    @Bean //配置了swagger的Docket的bean实例
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
                //.enable(false)
                .select()
                //RequestHandlerSelectors,配置要扫描接口的方式
                /* basePackage指定扫描的包;
                   any();扫描全部;
                   none();都不扫描;
                   withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象
                   withMethodAnnotation:扫描方法上的注解
                   .paths()过滤什么路径
                   .enable()是否启用Swagger,默认是true,若为false,则Swagger不能访问,并且会有一个小表情出现
                */
                .apis(RequestHandlerSelectors.basePackage("com.study.controller"))
                //.paths(PathSelectors.ant("/study/**"))
                .build();
    }

    //配置swagger信息:通过覆盖默认的apiInfo
    private ApiInfo apiInfo() {
        //配置作者的信息
        Contact contact = new Contact("全村第二帅", "https://blog.csdn.net/qq_53860947?type=blog", "[email protected]");
        return new ApiInfo("自定义api文档",
                "码道万古如长夜",
                "v3.0",
                "https://blog.csdn.net/qq_53860947?type=blog",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());

    }

}

8、运行结果

前端界面过多,这里不展示,可以借助前端模板完成

主界面:

当用户通过url地址栏进一个网站,能看见一些内容,但如果要点击这些内容,会弹出登录界面提示用户登录。当然不同用户能看到的内容不一样,比如当你充了钱,你肯定要享受更好的服务了,数据库应设置对应的权限表,当然这里为了方便,就增加了一个权限字段。

登录界面: 

 首先进行用户登录认证,如果登录名、密码和数据库的记录不一样,则会产生提示

授权界面: 

比如用户:李五,权限只能看图片,当然他只能点击看图片了 ,点击看文字,会跳到自己定制的错误401(没有权限)页面了。

​​​​​​​

 

再比如用户:无名,他就拥有看文字的权限了

SQL日志监控:

根据所配置的Druid后台用户名及密码登录到Druid的后台,然后查看SQL监控情况、SQL防火墙防御情况等相关信息。

Swagger API 日志文档:

注: 源码已上传至资源,有意者自取:shiro安全框架整合Mybatis-Java文档类资源-CSDN下载

猜你喜欢

转载自blog.csdn.net/qq_53860947/article/details/124307116