Shiro 相关

Shiro简介

Subject

  • 即当前的操作的“用户”,该用户是一个抽象概念,由 SecurityManager 管理,所有 Subject 都绑定到 SecurityManager

SecurityManager

  • 安全管理器,所以安全相关的交互都会经过 SecurityManager ,相当于springmvc中前端控制器DispatcherServlet;

Realm

  • ,SecurityManager从Realm获取安全数据(如用户、角色、权限)进行校验,可以把 Realm 看成 DataSource,即安全数据源

认证授权逻辑

  1. 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
  2. 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

整合springboot

依赖

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

配置类

配置步骤

  • 配置realm类
  • 配置SecurityManager管理器类(这里使用的是DefaultWebSecurityManager),关联上面配置的Realm
  • 配置ShiroFilterFactoryBean过滤工厂类,关联配置的SecurityManager、过滤器

创建配置类

  • shiroFilterFactoryBean.setFilterChainDefinitionMap配置过滤规则实现页面拦截
  • 过滤规则使用map集合装配,使用LinkedHashMap保证存取有序
  • shiroFilterFactoryBean.setLoginUrl设置登陆跳转页面
package com.hd.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

@Configuration
public class ShiroConfig {
    /**
     * 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

     /*
     * 添加shiro内置过滤器:
     * anon:无需认证
     * authc:必须认证才可以访问
     * user:如果使用remember的功能才可以访问
     * perms:该资源必须得到资源权限才可以访问
     * roles:该资源必须得到角色权限才可以访问
     */
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/css/**","anon");
        filterChainDefinitionMap.put("/js/**","anon");
        filterChainDefinitionMap.put("/img/**","anon");
        filterChainDefinitionMap.put("/register.html","anon");
        filterChainDefinitionMap.put("/public/login.html","anon");
        filterChainDefinitionMap.put("/userlogin","anon");
        filterChainDefinitionMap.put("/error/**","anon");
        filterChainDefinitionMap.put("/findOne","anon");

        //设置授权
        filterChainDefinitionMap.put("/user/**","roles[admin]");

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

        //修改跳转页面
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        //自定义授权页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth.html");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /**
     * 创建DefaultWebSecurityManager
     * @param userRealm
     * @return
     */
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建realm
     * @return 用户realm
     */
    @Bean(name="userRealm")
    public UserRealm getRealm(){
        return new UserRealm();
    }
}

自定义realm

认证

  • 继承AuthorizingRealm类,实现doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(认证)方法
  • Subject.login(token)登陆时,会跳转到认证授权的方法
package com.hd.config;

import com.hd.entity.Role;
import com.hd.entity.User;
import com.hd.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
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.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //  获取用户名
        String userName = (String)authenticationToken.getPrincipal();
        //  查询有无该用户
        User user = userService.findUserByName(userName);
        if (null == user) {
            // 没有该用户
            return null;  // 登陆时会抛出UnknownAccountException
        }

        /**
         * 密码认证
         * 参数1: 从数据库获取的userduix
         * 参数2: 密码
         * 参数3: 当前realm名称
         */
        return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
    }
}

授权

  • SecurityUtils.getSubject().getPrincipal():获取认证时传递的用户对象
  • SimpleAuthorizationInfo:授权信息对象
  • SimpleAuthorizationInfo.addStringPermission():设置当前用户的权限
import com.hd.entity.Auth;
import com.hd.entity.Role;
import com.hd.entity.User;
import com.hd.service.RoleService;
import com.hd.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
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.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    @Autowired
    RoleService roleService;

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 这里拿到的User对象是认证时设置的SimpleAuthenticationInfo类的参数
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        // 授权信息对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 获取用户角色
        Role role = userService.findRoleByName(user.getName());
        //todo:role=null ??

        // 将角色名称提供给info
        HashSet<String> roles = new HashSet<>();
        roles.add(role.getRole_name());
        info.setRoles(roles);

        // 获取角色所有权限,将权限名称提供给info
        List<Auth> auths = roleService.findAuthByRoleId(role.getRole_id());
        for (Auth auth :
                auths) {
            // 设置当前用户的权限
            info.addStringPermission(auth.getAuth_name());
        }

        return info;
    }

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //  获取用户名
        String userName = (String)authenticationToken.getPrincipal();
        //  查询有无该用户
        User user = userService.findUserByName(userName);
        if (null == user) {
            // 没有该用户
            return null;  // 登陆时会抛出UnknownAccountException
        }

        /**
         * 密码认证
         * 参数1: 从数据库获取的userduix
         * 参数2: 密码
         * 参数3: 当前realm名称
         */
        return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
    }
}

登陆

	/**
	 *	登录验证
	 */
	@PostMapping(value = "/userlogin")
	public String user_login(String username, String password, Model model){
		// 封装当前用户数据
		UsernamePasswordToken token = new UsernamePasswordToken(username,password);
		// 获取当前用户
		Subject currentUser = SecurityUtils.getSubject();

		try {
			// 执行登陆操作
			// 主体提交登录请求到SecurityManager
			currentUser.login(token);
		}catch (IncorrectCredentialsException ice){
			model.addAttribute("msg","密码不正确");
		}catch(UnknownAccountException uae){
			model.addAttribute("msg","账号不存在");
		}catch(AuthenticationException ae){
			model.addAttribute("msg","状态不正常");
		}
		if(currentUser.isAuthenticated()){
			return Result.ok("auth success");
		}else{
			token.clear();
			return Result.build(100001, "auth fail");
		}
	}

Shiro 内置过滤器及实现类

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

自定义Shiro Filter

  • 继承shiro过滤器(AccessControlFilter),重写onAccessDenied、isAccessAllowed
  • isAccessAllowed:判断是否登录,在登录的情况下会走此方法,此方法返回true直接访问控制器
  • onAccessDenied:是否是拒绝登录,没有登录的情况下会走此方法,此方法返回true直接访问控制器
public class UserFilter extends AccessControlFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //获取用户
        User sessionUser = (User) request.getSession().getAttribute("user");
        if (sessionUser != null) {
            //已经登陆,放行
            return true;
        } else {
            //在Cookie中获取token
            String loginToken = CookieUtil.findCookieByName(request, "loginToken");
            if (StringUtils.isNotBlank(loginToken)) {
                //根据token找用户
                UserService userService = (UserService)SpringContextUtil.getBean(UserService.class);
                User userByToken = userService.getUserByToken(loginToken);
                if (userByToken != null) {
                    //有对应token的用户,保存到session,放行
                    request.getSession().setAttribute("user", userByToken);
                    return true;
                } else {
                    //没有对应用户,清除Cookie
                    CookieUtil.clearCookie(request, response, "loginToken");
                    return false;
                }
            } else {

                //没有cookie,也没有登陆。是index请求获取用户信息,可以放行
                if (request.getRequestURI().contains("session")) {
                    return true;
                }

                //没有cookie凭证,跳转到登陆页
                response.sendRedirect("/login.html");
                return false;
            }
        }
    }
}

filter、realm中使用SpringBean

shiro中自定义的filter和realm中并没有被注入到Spring容器中,如果想要在filter中使用SpringBean,可以使用设置属性的方式

public class PasswordFilter extends AccessControlFilter{
    // 自定义的Filter中StringRedisTemplate来作为缓存的操作,此属性需要在Spring中获取
    private StringRedisTemplate redisTemplate;
    ...
}

**用已经注入到容器的类,去设置过滤器属性,即将stringRedisTemplate赋值给PasswordFilter **

@Component
public class ShiroFilterChainManager {

    private final StringRedisTemplate stringRedisTemplate;

    @Autowired
    public ShiroFilterChainManager(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public Map<String, Filter> initFilterMap() {
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        PasswordFilter passwordFilter = new PasswordFilter();
        passwordFilter.setRedisTemplate(stringRedisTemplate);
        filterMap.put("auth", passwordFilter);
        filterMap.put("jwt", jwtFilter);
        return filterMap;
    }
}

shiro配置类

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, ShiroFilterChainManager shiroFilterChainManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置过滤器链
        shiroFilterFactoryBean.setFilters(shiroFilterChainManager.initFilterMap());
      ...
}

注解

缓存管理

Memory

Redis

Session管理

posted @ 2020-11-14 15:03  熊云港  阅读(215)  评论(0编辑  收藏  举报