springboot中shiro 的应用

本文介绍了Apache Shiro在SpringBoot项目中的应用,强调其轻便和灵活的特点。内容涵盖了Shiro的基本功能,包括权限认证、授权、Session管理和加密。文章通过分析Shiro架构,展示了如何配置SecurityManager、Realm和Subject。还提到了使用Maven,依赖于MyBatis的数据源,并提供了部分关键代码示例,如自定义Realm和配置Shiro。最后,给出了TestController的代码,完成登录认证功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

apache shiro 是一个简单便捷的安全框架,也是官方推荐的安全框架,作用和spring scurity一样,但是却比scurity 轻便,灵活,由于spring boot 中没有整合shiro 所以如果我们要在项目中使用shiro需要自己配置一些东西 接下来开始介绍shiro 的使用

 

千里之行始于足下,先来认识shiro,上面这张图就是shiro官网的介绍图,可以很清楚的看出来 shiro基本功能包括四个方面

1. authentication(权限认证):通常用于用户登录

2. Autoorization(授权):主要就是给角色授权,就是说这个用户能做什么不能做什么

3. Session管理器:知道如何创建session或者如何管理session

4. Cryptography(加密):通过加密算法,对数据加密的同时,保证数据容易使用

当然也支持其他的一些功能(日后再聊)比方说支持缓存,记住密码,web支持,并发,支持单元测试,支持以另一个用户运行(在许可的情况下)

当然咯  shiro,他不会自己维护权限,这里需要我们去设计。接下来认识shiro代码

当我们的程序开始运行的时候,

1#首先会给到 subject,也就是当前应用程序的用户,是一个抽象的概念

2# SecurityManager 一个安全管理器,也是shiro中真正的执行者,管理着subject

3# realm 域 也就是安全的数据源,最简单的应用就是数据库

接下来看看shiro的架构图,当我们把这张图搞懂了,那么shiro 的学习就只剩下了解代码了

这张图不多介绍,基本的上面也都讲过,需要注意的一点就是,我们可以看得出基本这些shiro中的主体都是被security Manager管理着,

这里使用的是maven,贴上使用的dependency

                <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.10</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.7</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.16.22</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>4.3.13.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>



启动方式是用的maven的插件启动的,启动的时候要用maven中的springboot启动,如果直接点击运行,程序是无法找到jsp文件,而报404错误。

         <build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

 

接下来看代码了,数据库连接使用的是mybaits,所以实体类,mapper文件(放在resource下面),dao接口都是通过mybatis generator自动生成的,就不贴代码 了,不懂mybais逆向工程的请戳  mybaits逆向工程,直接贴一张图,这张图也是这个demo的所有java代码了

接下来就是application.properties配置文件中的内容

  #配置数据库连接
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=Root


#配置mabatis位置
mybatis.mapepr-location=mybatis/*.xml
mybatis.type-aliases-package=com.lj.shiromanagerweb.model
mybatis.config-location=classpath:mybatis-config.xml

#配置springmvc
spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp

 服务端只写了一个方法,

import com.lj.shiromanagerweb.dao.UserMapper;
import com.lj.shiromanagerweb.model.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author Eric
 * @date create in2018/8/7 21:06
 */
@Service
public class UserService {


    @Resource
    UserMapper userMapper;

    public User findByUserName(String username) {
        return userMapper.findByUsername(username);
    }

}

 需要说明的一点是因为这个地方查询数据库是夺标联查,所以,在mapper文件的这个方法稍稍有些不一样mapper中的findByuserName代码如下

<select id="findByUsername" parameterType="string" resultMap="userMap">
      SELECT u.*, r.*, p.*
      FROM user u
        INNER JOIN user_role ur on ur.uid = u.uid
        INNER JOIN role r on r.rid = ur.rid
        INNER JOIN permission_role pr on pr.rid = r.rid
        INNER JOIN permission p on pr.pid = p.pid
      WHERE u.username = #{username}
    </select>

重点是接下来的三个类中的代码

1.自定义realm文件 AuthRealm.java 主要做两件事,一件事就是从数据库查询出相关数据,来验证用户,第二件事就是获取相关权限,给用户授权

import com.lj.shiromanagerweb.model.Permission;
import com.lj.shiromanagerweb.model.Role;
import com.lj.shiromanagerweb.model.User;
import com.lj.shiromanagerweb.service.UserService;
import org.apache.commons.collections.CollectionUtils;
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.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * 自定义realm授权部分
 *
 * @author Eric
 * @date create in2018/8/7 21:30
 */
public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;



    /**
     * 授权方式
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //通过传进来的principals迭代获取用户
        User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
//        String userName = (String) principals.getPrimaryPrincipal();
        List<String> permissionList = new ArrayList<>();//
        List<String> roleNameList = new ArrayList<>();
//获取登录人的角色,通过相关角色查询权限
        Set<Role> roleSet = user.getRoles();
        if (CollectionUtils.isNotEmpty(roleSet)) {
            for (Role role : roleSet) {
                roleNameList.add(role.getRname());
                Set<Permission> permissionSet = role.getPermissions();
                if (CollectionUtils.isNotEmpty(permissionSet)) {
                    for (Permission permission : permissionSet) {
                        permissionList.add(permission.getPname());
                    }
                }
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionList);
        info.addRoles(roleNameList);
        return info;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String username = usernamePasswordToken.getUsername();
        User user = userService.findByUserName(username);
        return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());

    }


}

因为在shiro底层校验密码是通过SimpleCredentialsMatcher,那么这里我们也是实现了SimpleCredentialsMatcher,让校验的方式自己来实现,代码如下:

/**
 * 告诉我们密码的校验规则,密码校验规则由我们自己实现
 * @author Eric
 * @date create in2018/8/7 22:15
 */
public class CredentialMatcher extends SimpleCredentialsMatcher{

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String  password = new String(usernamePasswordToken.getPassword());
        String dbPassword = (String) info.getCredentials();
        return this.equals(password, dbPassword);
    }
}

接下来就需要写我们shiro中的一些配置,我们在ShiroConfiguration这个类里面注入,这样一来,shiro的校验读取都是按照我们写的方式来操作

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
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.LinkedList;

/**
 *
 * 定制springboot与shiro的关系,让系统在刚开始的时候就读取@Configuration
 *
 * 当程序开始的时候先读取shiroFilter--->securityManager---authRealm--->credentialMatcher
 * @author Eric
 * @date create in2018/8/7 22:21
 */
@Configuration
public class ShiroConfiguration {
    /**
     * anon:匿名访问
     * authc;form表单验证
     * authBasic:基础的http验证
     * logout:注销拦截器
     * noSessionCreation:没有session创建session的拦截器
     * perms:权限县验证
     * roles:角色验证
     * @param manager
     * @return
     */
   @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //登录页面
        bean.setLoginUrl("/login");
        //登录成功之后跳转的页面
        bean.setSuccessUrl("/index");
        //权限认证失败的时候跳转的页面
        bean.setUnauthorizedUrl("/unauthorized");
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //前面的url使用后面的过滤器
      /*  filterChainDefinitionMap.put("/index", "authc");
        filterChainDefinitionMap.put("/login", "anon");
       filterChainDefinitionMap.put("loginUser", "anon");
        filterChainDefinitionMap.put("/**", "user");*/
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

    /**
     * 使用我们自己的方式去验证一个用户
     * @param authRealm
     * @return
     */
    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    /**
     * 使用自定义realm 和自定义的密码校验规则
     * @param matcher
     * @return
     */
    @Bean("authRealm")
    public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher) {
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }

    /**
     * 校验密码的规则,这里是由自己定义的
     * @return
     */
    @Bean("credentialMatcher")
    public CredentialMatcher credentialMatcher() {
        return new CredentialMatcher();
    }

    /**
     * 让程序使用自定义的securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }


    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        //默认值是false
        creator.setProxyTargetClass(true);
        return creator;
    }

}

接下来及时springboot 的main方法   这个方法里面有一些需要注意的,及时天剑@MapperScan,这是mybatis中,让配置文件和dao联系在一起

@SpringBootApplication
@ComponentScan
@MapperScan(basePackages = {"com.lj.shiromanagerweb.dao"})
public class ShiroManagerWebApplication {

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

准备工作都已经做好了,那么接下来只剩下前段代码,和controller了,前段代码就省掉了

TestController.java 代码如下

import com.lj.shiromanagerweb.model.User;
import lombok.extern.slf4j.Slf4j;
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;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;


/**
 * @author Eric
 * @date create in2018/8/7 22:45
 */
@Controller
@Slf4j
public class TestController {

    @RequestMapping("/login")
    public String login() {
        return "login";

    }

    @RequestMapping("/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        if (subject!=null) {
            subject.logout();
        }
        return "logout";

    }

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


    @RequestMapping("/loginUser")
    public String loginUser(@RequestParam("username") String username,
                            @RequestParam("password") String password,
                            HttpSession session, Model model) {
        System.out.println("username = " + username);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();

        String username1 = new String(token.getUsername());
        String password1 = new String(token.getPassword());
        log.info("username1={};password1={}",username1,password1);

        try {
            subject.login(token);
            User user = (User) subject.getPrincipal();
            session.setAttribute("user", user);
            return "index";
        } catch (AuthenticationException e) {
            e.printStackTrace();
            model.addAttribute("msg", "用户名或者账号错误");
            return "login";
        }

    }

}

这样就大功告成了,,用maven中的springboot插件启动。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值