SpringBoot集成Shiro(超详细 -认证,动态授权,rememberMe)

SpringBoot集成Shiro(超详细)

本项目通过SpringBoot整合了shiro框架 使用MybatisPlus代码生成器生成简单的代码,项目包含shiro动态授权以及认证 ,rememberMe记住功能

一.项目准备

(1)导入依赖



	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.1.6.RELEASE
		
	
	com.example
	springboot-shiro
	0.0.1-SNAPSHOT
	springboot-shiro
	Demo project for Spring Boot Shiro

	
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
		

		
			mysql
			mysql-connector-java
			5.1.46
		

		
			com.baomidou
			mybatis-plus-boot-starter
			3.1.0
		

		
		
			com.baomidou
			mybatis-plus-generator
			3.1.0
		
		
			org.apache.velocity
			velocity-engine-core
			2.1
		

		
			junit
			junit
			4.12
		

		
		
			org.apache.shiro
			shiro-spring
			1.4.0
		
       
		
			org.springframework.boot
			spring-boot-devtools
			true
		
		
		
			net.sf.ehcache
			ehcache-core
			2.4.8
		
		
			org.apache.shiro
			shiro-ehcache
			1.4.0
		
		
		
			com.alibaba
			fastjson
			1.2.13
		
		
			org.springframework
			spring-context-support
			5.1.8.RELEASE
		


	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
				
				
					true
				
			
		
	



(2)设置SpringBoot配置文件

server.port=80

spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/

#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/crm?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mybatis-plus
mybatis-plus.type-aliases-package=com.example.leilei.entity

#热部署

#设置开启热部署
spring.devtools.restart.enabled=true
#页面不加载缓存,修改即时生效
spring.freemarker.cache=false

(3)设置SpringBoot启动类

@SpringBootApplication
@MapperScan(basePackages = "com.example.leilei.mapper")
public class SpringbootShiroApplication {

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

}

(4)Mybatis-plus的代码生成器使用

合理使用工具可以帮我减少开发时间

public class CodeGenerator {

    public static void main(String[] args) throws InterruptedException {
        //用来获取Mybatis-Plus.properties文件的配置信息
        ResourceBundle rb = ResourceBundle.getBundle("springboot-shiro");
        AutoGenerator mpg = new AutoGenerator();
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir(rb.getString("OutputDir"));
        gc.setFileOverride(true);
        gc.setActiveRecord(true);// 开启 activeRecord 模式
        gc.setEnableCache(false);// XML 二级缓存
        gc.setBaseResultMap(true);// XML ResultMap
        gc.setBaseColumnList(false);// XML columList
        gc.setAuthor(rb.getString("author"));
        mpg.setGlobalConfig(gc);
        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDbType(DbType.MYSQL);
        dsc.setTypeConvert(new MySqlTypeConvert());
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername(rb.getString("jdbc.user"));
        dsc.setPassword(rb.getString("jdbc.pwd"));
        dsc.setUrl(rb.getString("jdbc.url"));
        mpg.setDataSource(dsc);
        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
       // strategy.setTablePrefix(new String[] { "t_" });// 此处可以修改为您的表前缀
        strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
        strategy.setInclude(new String[]{"real_eseate"}); // 需要生成的表
        mpg.setStrategy(strategy);
        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent(rb.getString("parent"));
        pc.setController("controller");
        pc.setService("service");
        pc.setServiceImpl("service.impl");
        pc.setEntity("domain");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 注入自定义配置,可以在 VM 中使用 cfg.abc 【可无】
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
            }
        };

        List focList = new ArrayList();

        // 调整 xml 生成目录演示
        focList.add(new FileOutConfig("/templates/mapper.xml.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return rb.getString("OutputDirXml")+ "/cn/leilei/mapper/" + tableInfo.getEntityName() + "Mapper.xml";
            }
        });

        // 调整 query 生成目录
/*        focList.add(new FileOutConfig("/templates/query.java.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return rb.getString("OutputDomainDir")+ "/cn/leilei/query/" + tableInfo.getEntityName() + "Query.java";
            }
        });*/

        // 调整 domain 生成目录
        focList.add(new FileOutConfig("/templates/entity.java.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                return rb.getString("OutputDomainDir")+ "/cn/leilei/domain/" + tableInfo.getEntityName() + ".java";
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 自定义模板配置,可以 copy 源码 mybatis-plus/src/main/resources/templates 下面内容修改,
        // 放置自己项目的 src/main/resources/templates 目录下, 默认名称一下可以不配置,也可以自定义模板名称
        TemplateConfig tc = new TemplateConfig();
        //tc.setController("/templates/controller.java.vm");
        // 如上任何一个模块如果设置 空 OR Null 将不生成该模块。
        tc.setEntity(null);
        tc.setXml(null);
        mpg.setTemplate(tc);

        // 执行生成
        mpg.execute();
    }


}

springboot-shiro.perproties

#输出路径
OutputDir=E://EEworkspac//springbootshiro//springboot-shiro//src//main//java
#query和domain的生成路径
OutputDomainDir=E://EEworkspac//springbootshiro//springboot-shiro//src//main//java


#作者
author=lei
#数据源
jdbc.user=root
jdbc.pwd=root
jdbc.url=jdbc:mysql:///crm?useUnicode=true&characterEncoding=utf8

#包配置
parent=cn.leilei
#xml的生成的resources目录
OutputDirXml=E://EEworkspac//springbootshiro//springboot-shiro//src//main//resources

二.后端代码编写

(1)配置shiro的生命周期

这个shiro生命周期的Bean一定要单独配置

/**
 * 这个shiro生命周期的Bean一定要单独配置
 */
@Configuration
public class ShiroLifecycleBeanPostProcessorConfig {

    /**
     * Shiro生命周期处理器
     *
     * @return
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

}

(2)编写Realm实现认证,授权

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private IEmployeeService employeeService;
    @Autowired
    private IPermissionService permissionService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Employee employee = (Employee) principalCollection.getPrimaryPrincipal();
        Long empId = employee.getId();
        //根据员工id查询员工权限
        List permissions = permissionService.getByEmpId(empId);
		//将查询出的权限交给shiro
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        for (Permission permission : permissions) {
            info.addStringPermission(permission.getSn());
        }
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        //根据用户名查询用户
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        Employee employee = employeeService.getOne(wrapper);
        if(employee==null){
            throw new UnknownAccountException(username);
        }
        //封装info对象
        return new SimpleAuthenticationInfo(employee,employee.getPassword(), ByteSource.Util.bytes(MD5Utils.SALT),getName());
    }
}

授权方法需要连表查询

根据elmplyee 查询到permission表

简单的写了下,但在实际开发中sql语句不要使用 *

    

(3)shiro的配置类

目前项目中包含认证,授权,rememberMe

package com.example.leilei.config;

import com.example.leilei.entity.Permission;
import com.example.leilei.service.IEmployeeService;
import com.example.leilei.service.IPermissionService;
import com.example.leilei.shiro.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)//配置Bean加载的先后顺序
public class ShiroConfig {

    @Autowired
    private IPermissionService permissionService;

    /**
     * SecurityManager核心对象Bean
     * @return
     */
    @Bean(name = "securityManager")
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        //注入Realm
        securityManager.setRealm(myRealm());
        //注入记住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * 凭证比较器-加密加盐加次数
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//加密算法
        hashedCredentialsMatcher.setHashIterations(10);//加密次数
        return hashedCredentialsMatcher;
    }
    /**
     * cookie对象;
     * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。
     * @return
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        //System.out.println("ShiroConfiguration.rememberMeCookie()");
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

    /**
     * cookie管理对象;
     * rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        //System.out.println("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        return cookieRememberMeManager;
    }

    /**
     * 自定义的Realm
     * @return
     */
    @Bean
    public MyRealm myRealm(){
        MyRealm myRealm = new MyRealm();
        //设置密码加密凭证,登录时会对密码进行加密匹配
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myRealm;
    }

    /**
     *过滤器配置 过滤所有权限
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //配置过滤器
        Map filterChainDefinitionMap = new LinkedHashMap();
        filterChainDefinitionMap.put("/static/**", "anon");   //anon代表资源直接放行
        filterChainDefinitionMap.put("/logout", "logout");    //shiro的退出方法,会注销自己的认证

        //权限拦截,查出所有权限
        List permissions = permissionService.list();
        for (Permission permission : permissions) {
            filterChainDefinitionMap.put(permission.getUrl(),"perms["+permission.getSn()+"]");
        }

        filterChainDefinitionMap.put("/**", "authc");

        //当访问需要认证才能访问的资源,如果没有认证,则跳转到这个资源
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");

        //当访问需要授权才能访问的资源的时候,如果没有权限,则跳转到这个资源
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
}

(4)编写工具类

(1)Ajax

/**
 * 这个类用来返回ajax对象,并且私有化字段,只能通过构造方法赋值
 *
 */
public class AjaxResult {
    private Boolean success = true;
    private String msg;

    private AjaxResult() {
    }

    /**
     * 返回ajax
     * @return 没有错误就返回这个
     */
    public static AjaxResult success(){
        return new AjaxResult();
    }

    /**
     *  返回ajax
     * @param msg 错误信息
     * @return 有错误就返回这个
     */
    public static AjaxResult error(String msg){
        AjaxResult ajaxResult = success();;
        ajaxResult.setSuccess(false);
        ajaxResult.setMsg(msg);
        return ajaxResult;
    }

    public Boolean getSuccess() {
        return success;
    }

    private void setSuccess(Boolean success) {
        this.success = success;
    }

    public String getMsg() {
        return msg;
    }

    private void setMsg(String msg) {
        this.msg = msg;
    }
}

(2)Session域对象(登陆用户)存取值

public class SessionUtil {
    public static final String LOGINSESSION = "loginuser";

    //将登陆对象存入域对象之中
    public static void setSession(Employee employee){//将登陆用户存入域对象
        Subject subject = SecurityUtils.getSubject();//获取登陆对象
        subject.getSession().setAttribute(LOGINSESSION,employee);
    }

    //Session中获取当前登陆对象
    public static Employee getSession(){//获取登陆对象域对象信息
        Subject subject = SecurityUtils.getSubject();//获取登陆对象
        return (Employee) subject.getSession().getAttribute(LOGINSESSION);
    }
}

(3)MD5加密

public class MD5Utils {

    public static final String SALT = "fm";
    public static final int ITERATIONS = 10;

    /**
     * 加密
     * @param source
     * @return
     */
    public static String encrype(String source){
        SimpleHash simpleHash = new SimpleHash("MD5",source,SALT,ITERATIONS);
        return simpleHash.toString();
    }

    public static void main(String[] args) {

        System.out.println(encrype("admin"));

    }

}

(5)Controller层登陆注销方法

@Controller
public class LoginController {

    /**
     * 跳转到登录页面
     * @return
     */
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String login(){
    
        Subject subject = SecurityUtils.getSubject();
        //判断当前用户是否有使用rememberMe 
        if (subject.isRemembered()){
            return "main";
        //判断当前用户是否登录 
        }else if(subject.isAuthenticated()){
            return "main";
        }
        return "login";
    }

    /**
     * 登录请求
     * @param employee
     * @return
     */
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult login(@RequestBody Employee employee){
        //尝试获取获取用户信息
        Subject subject = SecurityUtils.getSubject();
        /*Boolean rememberMe = true;*/
        //将输入的账户密码封装为一个tocken对象
        UsernamePasswordToken token = new UsernamePasswordToken(employee.getUsername(),employee.getPassword(),employee.getRememberMe());
        try {
            //使用封装的tocken对象尝试通过shiro完成认证---->会调用自定义MyRealm类的AuthenticationInfo方法,尝试认证,
            if(!subject.isAuthenticated()){//判断当前用户是否登录 布尔值  取反
                subject.login(token);//认证登陆
            }
            //将登陆的用户信息存入域对象之中
            Employee logiuser = ((Employee) subject.getPrincipal());//获取当前用户
            SessionUtil.setSession(logiuser);
            return AjaxResult.success();
        }  catch (UnknownAccountException e) {
            e.printStackTrace();
            return AjaxResult.error("账户不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            return AjaxResult.error("密码错误");
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return AjaxResult.error("未知错误,检查后台");
        }
    }

    //退出登录
    @RequestMapping(value = "/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        return "redirect:/logout";
    }


}

三.基于VUE/ElementUI前端编写

前端所用的静态资源放在 resource/statc下

前端所用的页面放在 resource/templates下

(1)搭脚手架或者简单导入Elementui的js css

(2)登陆界面




    
    
    Title
    
    
    
    
    



其他页面按着编写就好 最终采用我的源码会完成动态授权以及认证以及rememberMe的功能实现

你可能感兴趣的:(shiro权限,SpringBoot)