springboot + shiro实现单点登录

本文详细介绍了如何在Spring Boot项目中使用Apache Shiro进行权限管理,包括配置ShiroFilter、自定义Realm、实现用户认证与授权,以及整合Thymeleaf方言等关键步骤。

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

文章目录

pom

   <dependency>
       <groupId>org.apache.shiro</groupId>
       <artifactId>shiro-spring</artifactId>
       <version>1.4.0</version>
   </dependency>

shiroconfig

package pers.jaye.springboot.config;

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

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zzrenfeng.classbrand.service.impl.shiro.ShiroRealm;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

/**
 * 
 * @Description _Shiro配置类
 * @author 田杰熠
 * @copyright {@link zzrenfeng.com}
 * @version 2018年4月13日 上午10:43:50
 * @see com.zzrenfeng.classbrand.config.ShiroConfig
 *
 */
@Configuration
public class ShiroConfig {

	@Bean
	public ShiroFilterFactoryBean shiroFilter() {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager());
		shiroFilterFactoryBean.setLoginUrl("/login"); // 未登录或者登录失败跳转路径
		
		//自定义Filter
		Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("authc", new LoginFormAuthenticationFilter());
        shiroFilterFactoryBean.setFilters(filters);



		// 拦截器.
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		filterChainDefinitionMap.put("/", "anon");
		filterChainDefinitionMap.put("/logout", "logout");
		filterChainDefinitionMap.put("/**", "authc");
		
		// 未授权url
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	/**
	 * securityManager 安全管理器
	 * 
	 * @return
	 */
	@Bean
	public DefaultWebSecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setSessionManager(sessionManager());
		securityManager.setRealm(shiroRealm());
		return securityManager;
	}

	/**
	 * 会话管理器 globalSessionTimeout session的失效时长(单位毫秒 ),deleteInvalidSessions
	 * 删除失效的session
	 * 
	 * @return
	 */
	@Bean
	public DefaultWebSessionManager sessionManager() {
		DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
		defaultWebSessionManager.setGlobalSessionTimeout(1800000);
		defaultWebSessionManager.setDeleteInvalidSessions(true);   //删除无效sessions
		defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false);  //禁止sessionId写入url
		return defaultWebSessionManager;
	}

	/**
	 * 身份认证realm; 将自定义的Realm注入到securityManager 安全管理器中。
	 * 
	 * @return
	 */
	@Bean
	public ShiroRealm shiroRealm() {
		ShiroRealm myShiroRealm = new ShiroRealm();
		myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return myShiroRealm;
	}

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

	/**
	 * LifecycleBeanPostProcessor将Initializable和Destroyable的实现类统一在其内部自动分别调用了Initializable
	 * .init() 和Destroyable.destroy()方法,从而达到管理shiro bean生命周期的目的。
	 * 
	 * @return
	 */
	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * 开启_Shiro的注解(如@RequiresRoles,@RequiresPermissions),
	 * 需借助SpringAOP扫描使用_Shiro注解的类,并在必要时进行安全逻辑验证 配置以下两个bean
	 * DefaultAdvisorAutoProxyCreator(可选 - 但经过测试是必须配置的具体原因 暂时还不了解)
	 * AuthorizationAttributeSourceAdvisor 即可实现此功能
	 * 
	 * @return
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		advisorAutoProxyCreator.setProxyTargetClass(true);
		return advisorAutoProxyCreator;
	}

	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

}

##shiroRealm

package pers.jaye.springboot.shiro;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.util.ByteSource;
import org.springframework.context.annotation.Lazy;

import com.zzrenfeng.classbrand.constant.Constant;
import com.zzrenfeng.classbrand.model.exception.ActivationAccountException;
import com.zzrenfeng.classbrand.model.shiro.ShiroUser;
import com.zzrenfeng.classbrand.model.sys.SysPermission;
import com.zzrenfeng.classbrand.model.sys.SysRole;
import com.zzrenfeng.classbrand.model.sys.SysUser;
import com.zzrenfeng.classbrand.service.sys.SysPermissionService;
import com.zzrenfeng.classbrand.service.sys.SysRoleService;
import com.zzrenfeng.classbrand.service.sys.SysUserService;

public class ShiroRealm extends AuthorizingRealm {

	@Lazy
	@Resource
	private SysUserService sysUserService;

	@Lazy
	@Resource
	private SysRoleService sysRoleService;

	@Lazy
	@Resource
	private SysPermissionService sysPermissionService;

	/**
	 * 身份认证
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String userCode = (String) token.getPrincipal();

		// 这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
		SysUser user = sysUserService.findByUserCode(userCode);
		if (user == null) {
			return null;
		}
		Integer state = user.getState();
		if (state.equals(Constant.USER_STATE_ACTIVATIONACCOUNT)) {
			throw new ActivationAccountException(Constant.USER_LOGIN_EXCEPTION_ACTIVATIONACCOUNT);
		} else if (state.equals(Constant.USER_STATE_DISABLEDACCOUNT)) {
			throw new DisabledAccountException(Constant.USER_LOGIN_EXCEPTION_DISABLEDACCOUNT);
		}

		ShiroUser shiroUser = new ShiroUser();
		shiroUser.setId(user.getId());
		shiroUser.setNickname(user.getNickname());
		shiroUser.setUserCode(user.getUserCode());
		shiroUser.setPassword(user.getPassword());
		shiroUser.setSalt(user.getSalt());
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(shiroUser, user.getPassword(), ByteSource.Util.bytes(user.getUserCode() + user.getSalt()), getName());
		return authenticationInfo;
	}

	/**
	 * 权限配置
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
		List<SysRole> roleList = sysRoleService.getRolesByUserId(shiroUser.getId());
		List<SysPermission> permissionList = sysPermissionService.getPermissionsByUserId(shiroUser.getId());

		Set<String> roles = roleList.stream().map(role -> role.getRole()).collect(Collectors.toSet());
		Set<String> permissions = permissionList.stream().map(permission -> permission.getPermission())
				.filter(string -> string != null).collect(Collectors.toSet());

		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

		authorizationInfo.setRoles(roles);
		authorizationInfo.setStringPermissions(permissions);

		return authorizationInfo;
	}

}

login

package com.zzrenfeng.classbrand.controller;

import java.util.Collection;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
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 com.zzrenfeng.classbrand.base.BaseController;
import com.zzrenfeng.classbrand.model.exception.ActivationAccountException;
import com.zzrenfeng.classbrand.service.impl.shiro.UserNamePasswordUserTypeToken;
import com.zzrenfeng.classbrand.service.sys.SysUserService;
import com.zzrenfeng.classbrand.util.Utils;
import com.zzrenfeng.classbrand.util.WriterUtils;

@Controller
public class LoginController extends BaseController {

	@Resource
	private Environment env;
	@SuppressWarnings("rawtypes")
	@Autowired
	private RedisTemplate redisTemplate;
	@Resource
	private SysUserService sysUserService;

	/**
	 */
	@RequestMapping("/login")
	public String login(HttpServletRequest request, Model model) throws Exception {
		if (isLogined()) {
			String jumpUrl = getJumpUrl(request);
			return "redirect:" + jumpUrl;
		}
		String errorLoginMsg = getErrorLoginMsg(request);
		model.addAttribute("msg", errorLoginMsg);
		return "/login";
	}

	/**
	 * 登录失败从request中获取shiro处理的异常信息。 shiroLoginFailure:就是shiro异常类的全类名.
	 * 
	 * @param request
	 * @return
	 */
	private String getErrorLoginMsg(HttpServletRequest request) {
		String exception = (String) request.getAttribute("shiroLoginFailure");
		String msg = "";
		if (exception != null) {
			if (UnknownAccountException.class.getName().equals(exception)) {
				msg = "账号不存在";
			} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
				msg = "密码不正确";
			} else if (DisabledAccountException.class.getName().equals(exception)) {
				msg = "账号已被禁用";
			} else if (ActivationAccountException.class.getName().equals(exception)) {
				msg = "账号尚未激活";
			} else {
				msg = exception;
			}
		}
		return msg;
	}



	/**
	 * 获得跳转路径
	 * 
	 * @param request
	 * @param subject
	 * @return
	 */
	private String getJumpUrl(HttpServletRequest request) {
	
		String url = "";
		SavedRequest savedRequest = WebUtils.getSavedRequest(request);
		if (savedRequest != null) {
			url = savedRequest.getRequestUrl();
			String ctx = request.getContextPath();
			url = url.substring(ctx.length());
		}

		return url;
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值