Spingboot+shiro+redis实现权限认证(附源码)

一、shiro和SpringSecurity的对比

目前比较流行的安全管理框架是shiro和SpringSecurity
据我了解所知shiro简单易用
SpringSecurity相对比较复杂,可扩展性好

二、shiro的作用

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

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


	
		org.springframework.boot
		spring-boot-starter-data-jpa
	
	
		org.springframework.boot
		spring-boot-starter-thymeleaf
	
	
		net.sourceforge.nekohtml
		nekohtml
		1.9.22
	
	
		org.springframework.boot
		spring-boot-starter-web
	
	
		org.apache.shiro
		shiro-spring
		1.4.0
	

	
	
		org.crazycake
		shiro-redis
		2.4.2.1-RELEASE
	
	
	
		com.alibaba
		fastjson
		1.2.13
	
	
		mysql
		mysql-connector-java
		runtime
	
	
		org.springframework.boot
		spring-boot-starter-test
		test
	
			
	
		
		
		
	
	
		org.apache.commons
		commons-lang3
		3.6
	

``

2.在application.yaml文件中配置thymeleaf和redis

spring:
    datasource:
      url: jdbc:mysql://localhost:8801/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: root
      #schema: database/import.sql
      #sql-script-encoding: utf-8
      driver-class-name: com.mysql.cj.jdbc.Driver
#创建数据库表
    jpa:
      database: mysql
      show-sql: true
      hibernate:
        ddl-auto: update
      properties:
         hibernate:
            dialect: org.hibernate.dialect.MySQL5Dialect

    thymeleaf:
       cache: false
       mode: HTML
    redis:
      host: 127.0.0.1
      port: 6379
      database: 0
server:
  port: 8087

3.在 shiroConfig类中借助redis配置会话管理、缓存管理和开启权限注解和url的权限和最大登录人数

package com.neo.config;

import org.apache.shiro.mgt.SecurityManager;//容易和java自带的包冲突
import javax.servlet.Filter;
import java.util.Properties;

@Configuration
public class ShiroConfig implements EnvironmentAware {

	private String host;
	private Integer port;

	@Override
	public void setEnvironment(Environment environment) {
		host = environment.getProperty("spring.redis.host");
		port = Integer.valueOf(environment.getProperty("spring.redis.port"));
		System.out.println("host==="+host+"port==="+port);
	}

	@Bean
	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
		System.out.println("ShiroConfiguration.shirFilter()");
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);

		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/login");//这里的是login的rest接口
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/index");

		//未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");

		//自定义拦截器
		Map filtersMap = new LinkedHashMap();
		//限制同一帐号同时在线的个数。
		filtersMap.put("kickout", kickoutSessionControlFilter());
		shiroFilterFactoryBean.setFilters(filtersMap);

		//拦截器.
		Map filterChainDefinitionMap = new LinkedHashMap();
		// 配置不会被拦截的链接 顺序判断
		filterChainDefinitionMap.put("/static/**", "anon");
		//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
		filterChainDefinitionMap.put("/logout", "logout");
		//:这是一个坑呢,一不小心代码就不好使了;
		//
		filterChainDefinitionMap.put("/login", "anon");
		filterChainDefinitionMap.put("/kickout", "anon");
		filterChainDefinitionMap.put("/**", "authc,kickout");
//		filterChainDefinitionMap.put("/**", "authc");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	/**
	 * 凭证匹配器
	 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
	 * )
	 * @return
	 */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher(){
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
		hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
		return hashedCredentialsMatcher;
	}

	@Bean
	public MyShiroRealm myShiroRealm(){
		MyShiroRealm myShiroRealm = new MyShiroRealm();
		//设置凭证匹配器
		myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return myShiroRealm;
	}

	@Bean
	public RedisManager redisManager(){
		RedisManager redisManager = new RedisManager();
		redisManager.setHost(host);
		redisManager.setPort(port);
		redisManager.setExpire(1800);
		return redisManager;
	}

	@Bean
	public RedisSessionDAO sessionDAO(){
		RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
		redisSessionDAO.setRedisManager(redisManager());
		return redisSessionDAO;
	}

	//缓存管理器
	@Bean
	public RedisCacheManager cacheManager(){
		RedisCacheManager cacheManager = new RedisCacheManager();
		cacheManager.setRedisManager(redisManager());
		return cacheManager;
	}

	@Bean
	public DefaultWebSessionManager sessionManager(){
		//跨域session共享
//		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		SessionManager sessionManager = new SessionManager();
		sessionManager.setSessionDAO(sessionDAO());
		return  sessionManager;
	}

	@Bean
	public SecurityManager securityManager(){
		DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();

		// 自定义缓存实现 使用redis
		securityManager.setCacheManager(cacheManager());
		// 自定义session管理 使用redis
		securityManager.setSessionManager(sessionManager());
		//有说法securityManager.setRealm放在最后,否则可能会出现无法调用权限认证方法
		securityManager.setRealm(myShiroRealm());
		return securityManager;
	}

	/**
	 *  开启shiro aop注解支持.(如@RequiresRoles,@RequiresPermissions)使用代理方式;所以需要开启代码支持;
	 *
     * 使授权注解起作用不如不想配置可以在pom文件中加入
     * 
     *org.springframework.boot
     *spring-boot-starter-aop
     *
	 *
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

// 限制同一账号登录同时登录人数控制
	@Bean
	public KickoutSessionControlFilter kickoutSessionControlFilter() {
		KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
		kickoutSessionControlFilter.setCacheManager(cacheManager());
		kickoutSessionControlFilter.setSessionManager(sessionManager());
		kickoutSessionControlFilter.setKickoutAfter(false);
		kickoutSessionControlFilter.setMaxSession(1);
		kickoutSessionControlFilter.setKickoutUrl("/kickout");//这里的是rest接口
		return kickoutSessionControlFilter;
	}

	@Bean(name="simpleMappingExceptionResolver")
	public SimpleMappingExceptionResolver
	createSimpleMappingExceptionResolver() {
		SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
		Properties mappings = new Properties();
		mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
		mappings.setProperty("UnauthorizedException","403");
		r.setExceptionMappings(mappings);  // None by default
		r.setDefaultErrorView("error");    // No default
		r.setExceptionAttribute("ex");     // Default is "exception"
		//r.setWarnLogCategory("example.MvcLogger");     // No default
		return r;
	}
}

4.自定义realms实现身份认证和权限认证

package com.neo.config;
public class MyShiroRealm extends AuthorizingRealm {
    @Resource
    private UserInfoService userInfoService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
        for(SysRole role:userInfo.getRoleList()){
            authorizationInfo.addRole(role.getRole());
            for(SysPermission p:role.getPermissions()){
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }

    /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();
        System.out.println(token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo = userInfoService.findByUsername(username);
        System.out.println("----->>userInfo="+userInfo);
        if(userInfo == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                userInfo, //用户名
                userInfo.getPassword(), //密码
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                getName()  //realm name
        );
        return authenticationInfo;
    }
}

5.在Controller类中实现对登录、登出等请求接口指定跳转页面。

package com.neo.web;

@Controller
public class HomeController {

    @Autowired
    LoginService loginService;

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

    @RequestMapping(value = "/login",method = RequestMethod.GET)
    public String toLogin(Map map,HttpServletRequest request)
    {
        loginService.logout();
        return "/login";
    }

    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public String login(Map map,HttpServletRequest request) throws Exception{
        System.out.println("login()");
        String userName = request.getParameter("userName");
        String password = request.getParameter("password");

        LoginResult loginResult = loginService.login(userName,password);
        if(loginResult.isLogin())
        {
            return "/index";
        }
        else {
            map.put("msg",loginResult.getResult());
            map.put("userName",userName);
            return "/login";
        }
    }

    //被踢出后跳转的页面
    @RequestMapping(value = "/kickout", method = RequestMethod.GET)
    public String kickOut() {
        return "/kickOut2";
    }

    @RequestMapping("/403")
    public String unauthorizedRole(){
        System.out.println("------没有权限-------");
        return "403";
    }
}

package com.neo.web;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/userInfo")
public class UserInfoController {

/**
 * 用户查询.
 * @return
 */
@RequestMapping("/userList")
@RequiresPermissions("userInfo:view")//权限管理;
public String userInfo(){
    return "userInfo";
}

/**
 * 用户添加;
 * @return
 */
@RequestMapping("/userAdd")
@RequiresPermissions("userInfo:add")//权限管理;
public String userInfoAdd(){
    return "userInfoAdd";
}

/**
 * 用户删除;
 * @return
 */
@RequestMapping("/userDel")
@RequiresPermissions("userInfo:del")//权限管理;
public String userDel(){
    return "userInfoDel";
}

}

github源码地址:https://github.com/nvhanzijiuba/springboot-examples.git

注意:此项目先启动Application类来生成数据库表,之后再导入import.sql语句来插入数据。

你可能感兴趣的:(springboot)