springboot + shiro реализует аутентификацию входа и контроль разрешений

Shiro

предисловие

В этот период времени я изучал springboot, и среди spring security и shiro выбрал shiro, по причине того, что shiro имеет низкую стоимость обучения и может не иметь мощных функций Spring Security, но может и не требовать таких сложных вещей в реальной работе, а степень детализации Coarse также можно настроить по мере необходимости, поэтому достаточно использовать небольшой и простой Shiro. Эта статья в основном относится к z77zАутентификация при входе и контроль разрешений интегрированного обучения SpringBoot+shiro Адрес проекта исходного кода

О Широ

Ядро Широ:

  • Субъект (subject): Используется для записи текущего работающего пользователя. Субъект — это интерфейс в shiro. Интерфейс определяет множество методов, связанных с аутентификацией и авторизацией. Внешние программы аутентифицируются и авторизуются субъектом, а субъект аутентифицируется SecurityManager.
  • SecurityManager (менеджер безопасности): Управляйте субъектом. Он является ядром shiro. SecurityManager — это интерфейс, который наследует три интерфейса Authenticator, Authorizer и SessionManager.
  • Аутентификатор: аутентификация личности пользователя
  • Авторизатор: после аутентификации пользователя считается, что у него есть это разрешение.
  • область: получить данные о правах пользователя
  • SessionManager (управление сеансом): фреймворк shiro определяет набор управления сеансом, который не зависит от сеанса веб-контейнера, поэтому shiro можно использовать в не-веб-приложениях или можно управлять сеансами распределенных приложений одновременно. Эта функция позволяет реализовать единый вход.
  • CacheManager: сохраняет данные разрешений пользователя в кэше, что может повысить производительность.

цель обучения

Выполните аутентификацию пользователя при входе. Если аутентификация не удалась, он вернется к интерфейсу входа. Только успешная аутентификация и пользователь имеет право доступа к указанной ссылке. В противном случае он перейдет на страницу 403.

добавить зависимости

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

Добавить конфигурацию широ

Пользовательский перехватчик формы MyFormAuthenticationFilter также добавлен в конфигурацию для обработки информации об исключении входа в систему и возврата ее путем возврата

ShiroConfig.java

/**
 * @author wgc
 * @date 2018/02/09
 */
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 配置不会被拦截的链接 顺序判断

        filterChainDefinitionMap.put("/assets/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        filterChainDefinitionMap.put("/layui/**", "anon");
        filterChainDefinitionMap.put("/captcha/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");

        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        // <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问;
        // user:认证通过或者记住了登录状态(remeberMe)则可以通过
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        
        //自定义拦截器
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("authc", new MyFormAuthenticationFilter());
        return shiroFilterFactoryBean;
    }

    /**
	 * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
	 * @return myShiroRealm
	 */
    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        return myShiroRealm;
    }

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

Реализация области

Широ получает данные безопасности (такие как пользователи, роли, разрешения) из Realm, то есть, если SecurityManager хочет проверить личность пользователя, ему необходимо получить соответствующего пользователя из Realm для сравнения, чтобы определить, является ли личность пользователя законной. ; ему также необходимо получить соответствующий идентификатор пользователя от Realm. Роли/разрешения используются для проверки того, может ли пользователь работать; Realm можно рассматривать как DataSource, то есть безопасный источник данных. Realm имеет два основных метода:

  • doGetAuthorizationInfo (получить информацию об авторизации)
  • doGetAuthenticationInfo (получить информацию, связанную с аутентификацией):

Реализация пользовательской области

/**
 * @author wgc
 * @date 2018/02/09
 */
public class MyShiroRealm extends AuthorizingRealm {
    private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
    @Resource
    private ShiroService userInfoService;
    // 权限授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal();
		for(SysRole sysRole : userInfoService.findSysRoleListByUsername(userInfo.getUsername())){
			authorizationInfo.addRole(sysRole.getRolename());
			logger.info(sysRole.toString());
			for(SysPermission sysPermission : userInfoService.findSysPermissionListByRoleId(sysRole.getId())){
				logger.info(sysPermission.toString());
				authorizationInfo.addStringPermission(sysPermission.getUrl());
			}
		};
        return authorizationInfo;
    }

    //主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        //获取用户的输入的账号.
        String username = (String)token.getPrincipal();
        logger.info("对用户[{}]进行登录验证..验证开始",username);
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserInfo userInfo =  userInfoService.selectUserInfoByUsername(username);
        if(userInfo == null){
        	// 抛出 帐号找不到异常  
            throw new UnknownAccountException();  
        }        
        return new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), getName());
    }
}

проверка пользовательской формы

Здесь мы входим в систему, используя форму сообщения на странице входа.Поскольку shiro имеет встроенный FormAuthenticationFilter, который представляет собой фильтр аутентификации на основе формы, если он не настроен, будет вызываться этот фильтр shiro по умолчанию. Поскольку нам нужно вернуть информацию об ошибке входа в систему, нам нужно наследовать и настроить фильтр формы, переписать метод setFailureAttribute (этот метод будет вызываться при ошибке входа) и записать информацию об ошибке входа в атрибут «shiroLoginFailure» запрос Титульный лист вынуть распечатать.

Реализация фильтра проверки подлинности формы

/**
 * 自定义表单认证
 * @author wgc
 */
public class MyFormAuthenticationFilter extends FormAuthenticationFilter{
	private static final Logger logger = LoggerFactory.getLogger(MyFormAuthenticationFilter.class);
	/**
	 * 重写该方法, 判断返回登录信息
	 */
    @Override
    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        String className = ae.getClass().getName();
        String message;
        String userName = getUsername(request);
        if (UnknownAccountException.class.getName().equals(className)) {
        	logger.info("对用户[{}]进行登录验证..验证未通过,未知账户", userName);
            message = "账户不存在";
        } else if (IncorrectCredentialsException.class.getName().equals(className)) {
        	logger.info("对用户[{}]进行登录验证..验证未通过,错误的凭证", userName);
            message = "密码不正确";
        } else if(LockedAccountException.class.getName().equals(className)) {
        	logger.info("对用户[{}]进行登录验证..验证未通过,账户已锁定", userName);
        	message = "账户已锁定";
        } else if(ExcessiveAttemptsException.class.getName().equals(className)) {
        	logger.info("对用户[{}]进行登录验证..验证未通过,错误次数过多", userName);
        	message = "用户名或密码错误次数过多,请十分钟后再试";
        } else if (AuthenticationException.class.getName().equals(className)) {
        	//通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
        	logger.info("对用户[{}]进行登录验证..验证未通过,未知错误", userName);
        	message = "用户名或密码不正确";
        } else{
        	message = className;
        }
        request.setAttribute(getFailureKeyAttribute(), message);
    }
}

веб-связанный

страница авторизации

controller

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

login.html

<!DOCTYPE html>
<html  lang="en" class="no-js" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="utf-8"/>
	<title>登录--layui后台管理模板</title>
	<link rel="stylesheet" href="../../layui/css/layui.css" media="all" />
	<link rel="stylesheet" href="../css/login.css" media="all" />
</head>
<body>
<div class="login">
	<h1>layuiCMS-管理登录</h1>
	<form class="layui-form" method="post">
		<div class="layui-form-item">
			<input class="layui-input" name="username" placeholder="用户名" type="text" autocomplete="off"/>
		</div>
		<div class="layui-form-item">
			<input class="layui-input" name="password" placeholder="密码" type="password" autocomplete="off"/>
		</div>
		<button class="layui-btn login_btn" lay-submit="" lay-filter="login">登录</button>
	</form>
</div>
<script type="text/javascript" src="../layui/layui.js"></script>

<script th:inline="javascript">
layui.use(['layer'], function(){
    var layer = layui.layer;
    var message = [[${shiroLoginFailure}]]?[[${shiroLoginFailure}]]:getUrlPara("shiroLoginFailure");
    if(message) {
    	layer.msg(message);
    }        	
});
function getUrlPara(name)
{
    var url = document.location.toString();
    var arrUrl = url.split("?"+name +"=");
    var para = arrUrl[1];
    if(para)
    	return decodeURI(para);
}
</script>
</body>
</html>

Главная страница

controller

@RequestMapping({"/","/index"})
public String index(Model model) {
    UserInfo user = (UserInfo)SecurityUtils.getSubject().getPrincipal();
    model.addAttribute("user", user);
    return "index";
}

index.html

<!DOCTYPE html>
<html lang="en" class="no-js" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8"/>
  <title>主页面</title>
</head>
<body >
<h3 th:text="${user.username}">user</h3>
</body>
</html>

контрольная работа

задача перваяПосле запуска откройте любую ссылку под адресом: 8080, и она перейдет на страницу входа, потому что она не зарегистрирована. перейти на страницу входа.

задача втораяПосле входа в систему успешно перейдите на страницу добавления и измените разрешение доступа для добавления в файле конфигурации shiro на

filterChainDefinitionMap.put("/add","perms[权限删除]");

Перезапустите программу и войдите в нее после входа в систему, она будет перенаправлена ​​​​на страницу / 403. Поскольку страница 403 не записана, сообщается об ошибке 404. Вышеуказанные операции активируют метод аутентификации авторизации: MyShiroRealm.doGetAuthorizationInfo(), который будет запускаться каждый раз, когда вы к нему обращаетесь.