SpringBoot_Shiro

Shiro?

        Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序

        Shiro中有三大对象:Realm、Subject、SecuritytManager

        绝大部分对于项目的说明写在代码注释中

        此博客中的项目基于SpringBoot(2.6.7)整合Mybatis项目创建,其中大部分依赖版本依据SpringBoot(2.6.7)而定,小部分官方未提供版本建议需自行指定

一.导入依赖

 <dependencies>
        <!--thymeleaf依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--thymeleaf与shiro整合包-->
        <!--可用thymeleaf进行验证(需要命名空间xmlns:sec="http://www.thymeleaf.org/extras/spring-security")-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.1.0</version>
        </dependency>

        <!--web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--shiro依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.9.0</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--mysql connector-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <version>2.6.7</version>
        </dependency>
    </dependencies>

二.配置yml文件

server:
  port:
    8080
  
mybatis:
  mapper-locations: classpath:mappers/*.xml
  type-aliases-package: cn.alan.springbootshiro.POJO

spring:
  #选择启用的环境
  profiles:
    active: dev
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456789
    #德鲁伊数据源
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      #SpringBoot默认是不注入 需要自己绑定至bean(使用java配置bean时 因为springboot内置了servlet容器 所以无web.xml 需要@Bean将配置注入)
      #druid数据源专有配置
      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:防御sql注入、log4j:日志记录
      filters: stat,wall,log4j
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
      #配置 DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: /druid/*,*.js,*.css,*.gif,*.jpg,*.bmp,*.png,*.ico
      #配置 DruidStatViewServlet
      stat-view-servlet:
        #访问德鲁伊监控页面的地址
        url-pattern: /druid/*
        #IP白名单 没有配置或者为空 则允许所有访问
        allow: 127.0.0.1
        #IP黑名单 若白名单也存在 则优先使用
#        deny: ip地址
        #禁用重置按钮
#        reset-enable: false
        #登录德鲁伊监控页面所用的用户名与密码
        login-username: root
        login-password: 123456
  #关闭thymeleaf缓存 修改代码后无需重启即可更新
  thymeleaf:
    cache: false

三.代码部分

DAO层(注意@Repository与@Mapper注解)

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

Service层(注意@Service注解)

        Service实现类需要实现UserDetails接口 

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

Controller层(注意@Controller注解)

package cn.alan.springbootshiro.Controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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 RoutingController {
    @RequestMapping({"/", "/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello shiro");
        return "index";
    }

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

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

    //登陆页面
    @RequestMapping("/toLogin")
    public String toLogin(Model model){
        return "login";
    }

    //登录处理(处理login.html中表单提交的数据)
    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //通过Subject获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //将当前用户的登录数据封装为token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);

        //将token传入subject的login方法 若无异常则完成登录
        try {
            subject.login(usernamePasswordToken);
            //无异常 返回首页
            return "index";
        } catch (AuthenticationException e) {
            //存在异常 停留在登陆页面 并输出提示
            model.addAttribute("msg","wrong username or password");
            return "login";
        }
    }

    //注销 logout
    @RequestMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "index";
    }

    //未授权页面
    @RequestMapping("/unAuthorized")
    public String unAuthorized(){
        return "unAuthorized";
    }
}

POJO

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    int id;
    String username;
    String password;
    String perms;
    String role;
}

Config

        Realm类需要继承AuthorizingRealm类

//自定义realm对象 继承AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权Authorization");

        //授权信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //获取当前进行登录的对象
        Subject subject = SecurityUtils.getSubject();
        //获取User对象
        User currentUser = (User) subject.getPrincipal();
        //设置当前用户权限
        simpleAuthorizationInfo.addStringPermission(currentUser.getPerms());
        simpleAuthorizationInfo.addRole(currentUser.getRole());

        return simpleAuthorizationInfo;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证Authentication");

        //将token转换为UsernamePasswordToken
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        //从数据库取值
        User user = userService.queryUserByName(usernamePasswordToken.getUsername());

        if (user==null){
            throw new AuthenticationException("unknown username");//抛出异常
        }

        //密码认证 由shiro完成
        //可以进行加密 默认为Simple加密(SimpleCredentialsMatcher)
        //第一个参数为principal 可在Subject对象中通过getPrincipal()接受
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }
}
package cn.alan.springbootshiro.Config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class ShiroConfig {
    //创建Realm对象 需要自定义
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    //DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //SecurityManager关联realm
        defaultWebSecurityManager.setRealm(userRealm());
        return defaultWebSecurityManager;
    }

    //ShiroFilterFactoryBean 过滤器(授权)
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //Filter关联SecurityManager
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());

        //shiro内置过滤器
        /*
            anon: 无需认证即可访问
            authc: 必须认证了才能访问
            user: 必须拥有记住我功能才能访问
            perms: 拥有对某个资源的访问权限才能访问
            role: 拥有某个角色权限才能访问
         */

        //收集页面授权信息
        Map<String,String> filterMap = new LinkedHashMap<>();
        //权限验证 若无权限则跳转至未授权页面
        //filterMap.put("/user/add","roles[user01]");//必须拥有user角色才能访问
        //filterMap.put("/user/update","perms[add,update]");//必须同时拥有add与update权限才能访问
        filterMap.put("/user/add","perms[add]");//必须拥有add权限才能访问
        filterMap.put("/user/update","perms[update]");//必须拥有update权限才能访问
        //注:数据库字段若为通配符 则代表拥有所有角色或权限

        //设置页面授权
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //设置登录页面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        //设置未授权跳转页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unAuthorized");

        return shiroFilterFactoryBean;
    }

    //整合ShiroDialect 用于整合shiro与thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}

数据库

资源目录结构

 index.html

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
>
<head>
    <meta charset="UTF-8">
    <title>title</title>
</head>
<body>

<h1>index</h1>
<!--从controller中取值(处理index.html页面的mapping)-->
<p th:text="${msg}"></p>
<hr>
<div shiro:guest="true"><!--游客访问时显示-->
    <a th:href="@{/toLogin}">Login</a>
</div>
<div shiro:authenticated="true"><!--登录后显示-->
    <a th:href="@{/logout}">Logout</a>
</div>
<div shiro:hasPermission="add"><!--拥有add权限时显示-->
    <a th:href="@{/user/add}">Add</a>
</div>
<div shiro:hasPermission="update"><!--拥有update权限时显示-->
    <a th:href="@{/user/update}">update</a>
</div>

</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Login</h1>
<!--从controller中取值(处理login.html的mapping处)-->
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
    <input type="text" name="username"><strong>Username</strong>
    <br>
    <br>
    <input type="text" name="password"><strong>Passwrod</strong>
    <p><input type="submit" value="Submit"></p>
</form>
</body>
</html>

add.html(update.html、unAuthorized.html与此大同小异,不赘述)

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

运行效果

访问localhost:8080进入首页,点击登录按钮进入登录页面

登录页面

登录失败提示

不同的账户拥有的权限、角色不同,看到的界面亦不同

 

注销后返回首页 

至此,页面授权与登录认证(数据库取值)均完成

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值