Аутентификация входа в систему контроля разрешений Широ
Предисловие: я считаю, что большинство студентов, которые приходят, просто плохо знакомы со структурой широ.
Итак, давайте начнем с основ.Конечно, я отложу в сторону официальные изображения сиро (кто-то действительно будет смотреть на эту штуку?), и объясню вам пошагово.Процесс настройки Широиаутентификация входапростая реализация
Широ используется, чтобы помочь нам управлять разрешениями Широ в этой статье используется в веб-проектах, поэтому я использовал последнюю весеннюю загрузку в качестве основы. (Конечно, для настройки можно использовать и xml, принцип тот же, но способ написания другой)
Поймите, что такое управление разрешениями Широ, прежде чем начать учиться?
Мы знаем, что основными функциями shiro являются аутентификация, авторизация, шифрование, управление сессиями, кэширование и т.д.
Многие функции заставят вас чувствовать себя неаппетитно, чтобы узнать, здесь мы в основном знаем, чтоСертификация,Разрешитьпросто хорошо
(Конечно, так понимать не совсем точно, но так легче понять)
Аутентификация — это аутентификация при входе: когда вы входите на эту веб-страницу, shiro будет аутентифицировать вас с помощью пароля (здесь мы используем токен), конечно, вы также будете использовать этот пароль, чтобы получить одобрение сервера для последующих операций авторизации;
Авторизация — это принятие разрешения: shiro предоставит вам соответствующие права (такие как удаление, добавление и т. д.) после аутентификации в соответствии с предоставленной вами информацией;
Помните, что Широ не будет создавать и поддерживать для вас реляционные таблицы, нам нужно самим создать соответствующие реляционные таблицы в базе данных:Пользователь——Роль——разрешение
Давайте посмотрим на эти таблицы:
1.user (таблица пользователей)
2.role (таблица ролей)
3.permission (таблица разрешений)
Между пользователями и ролями существует отношение «один ко многим», и у пользователя может быть несколько ролей (например, администраторы, обычные пользователи).
Роли и разрешения находятся в отношениях «многие ко многим».Роль может использовать несколько разрешений, а разрешение также может соответствовать нескольким пользователям.
Конечно, есть и ассоциативные таблицы, тут особо нечего сказать,Потому что мы делаем только проверку входа, так что на данный моментТребуется только одна пользовательская таблицаПросто
Так что жеаутентификация входа, я думаю, что многие новички неправильно поймут его значение, оно не для того, чтобы помочь вам войти в учетную запись пользователя.
Чтобы действительно понять это, мы должны знатьдля чего используется сиро? Какова роль аутентификации при входе в Широ?
Как упоминалось ранее, shiro используется для управления разрешениями, и как я могу это сделать после входа в систему?shiroВсегда помните вас, это роль аутентификации входа
Тогда некоторые студенты спросят, зачем использовать широ-аутентификацию вместо использования пользовательской таблицы базы данных для аутентификации?
Я тоже задавал этот вопрос, и если вы продолжите понимать, то узнаете:
Поскольку при каждой последующей операции вы будете использовать данные, возвращенные вам сервером для проверки, крайне небезопасно и ненадежно использовать данные таблицы User.Поскольку добавляется структура shiro, необходимо учитывать безопасность, поэтому мы будем использоватьtokenпроверить, что также является предметом этой статьифокус!
Без лишних слов, приступим:
Шаг 1. Импортируйте связанные пакеты
Здесь я использую maven для управления пакетами:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.lxt</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>1.5.8.RELEASE</version>
<optional>true</optional>
<scope>true</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.5</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
Шаг 2: Настройте Широ
После того, как pom настроен, мы будем использовать java для написания класса глобальной конфигурации shiro.
Прежде чем настраивать сиро, нам нужно понять три его основных элемента:
Subject: отдельный объект, пользовательский объект, взаимодействующий с тем, как приложение;
SecurityManager: Менеджер безопасности, управление Темой;
Realm: Домен, SecurityManager взаимодействует с Realm для получения данных (разрешение роли пользователя)
Зная это, мы начинаем создавать новый класс ShiroConfig: (поскольку в этой статье изучается только аутентификация при входе в систему, поэтому нам сначала не нужно управление кешем, кодирование паролей и другие функции)
package cn.lxt.shiro;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class shiroConfig {
/**
* 负责shiroBean的生命周期
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
*这是个自定义的认证类,继承子AuthorizingRealm,负责用户的认证和权限处理
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public MyShiroRealm shiroRealm(){
MyShiroRealm realm = new MyShiroRealm();
//realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
/** 安全管理器
* 将realm加入securityManager
* @return
*/
@Bean
public SecurityManager securityManager(){
//注意是DefaultWebSecurityManager!!!
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
/** shiro filter 工厂类
* 1.定义ShiroFilterFactoryBean
* 2.设置SecurityManager
* 3.配置拦截器
* 4.返回定义ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
//1
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//2
//注册securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
System.out.println("11");
//3
// 拦截器+配置登录和登录成功之后的url
//LinkHashMap是有序的,shiro会根据添加的顺序进行拦截
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//配置不会被拦截的连接 这里顺序判断
//anon,所有的url都可以匿名访问
//authc:所有url都必须认证通过才可以访问
//user,配置记住我或者认证通过才能访问
//logout,退出登录
filterChainDefinitionMap.put("/JQuery/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
//配置退出过滤器
filterChainDefinitionMap.put("/example1","anon");
filterChainDefinitionMap.put("/lxt","anon");
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("/success","anon");
filterChainDefinitionMap.put("/index","anon");
filterChainDefinitionMap.put("/Register","anon");
filterChainDefinitionMap.put("/logout","logout");
//过滤连接自定义,从上往下顺序执行,所以用LinkHashMap /**放在最下边
filterChainDefinitionMap.put("/**","authc");
//设置登录界面,如果不设置为寻找web根目录下的文件
shiroFilterFactoryBean.setLoginUrl("/lxt");
//设置登录成功后要跳转的连接
shiroFilterFactoryBean.setSuccessUrl("/success");
//设置登录未成功,也可以说无权限界面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("shiro拦截工厂注入类成功");
//4
//返回
return shiroFilterFactoryBean;
}
}
Несколько вещей, которые следует отметить выше:
1.shiroFilter - это вход, есть четыре основных шага, код четко аннотирован
2.shiroFilterFactoryBean.setLoginUrl("/lxt");Класс запуска Независимо от того, какой URL-адрес вы вводите, он перейдет к классу запуска входа;
3.shiroFilterFactoryBean.setSuccessUrl("/success"); Класс, который прыгает после успешного входа в систему, вы можете игнорировать этот метод, потому что я не думаю, что он вообще используется, не распыляйтесь!
Шаг 3: Настройте область
Посмотрев на класс ShiroConfig, многие спросят: ой! Почему мой MyShiroRealm не может быть импортирован!
На самом деле вызов этого метода требует от нас написания класса Realm для наследования AuthorizingRealm.
После наследования нам нужно переопределить два метода:
1. Метод doGetAuthorizationInfo() используется для управления ролями и разрешениями и в настоящее время не используется;
2. метод doGetAuthenticationInfo() используется для аутентификации при входе в систему,фокус.
Вставьте код ниже:
package cn.lxt.shiro;
import cn.lxt.bean.User;
import cn.lxt.service.UsersService;
import org.apache.shiro.authc.*;
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;
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UsersService usersService;
/**
* 用于获取登录成功后的角色、权限等信息
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 验证当前登录的Subject
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//拿到账号(username)
String username = (String) token.getPrincipal();
System.out.println("username=:"+username);
//检查token的信息
System.out.println(token.getCredentials());
User user = usersService.findByName(username);
if (user==null){
return null;
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),getName());
return info;
}
}
Через приведенный выше код вы узнаете, как мы проверяем, точкой связи для проверки является токен входящего параметра.
Теперь все должны понятьtokenроль в этой статье!
Конечно, некоторые студенты все еще сбиты с толку, когда видят это, поэтому я объясню здесь некоторые идеи:
1. Когда мы войдем в систему с учетной записью и паролем, будет создан токен (токен — это просто концепция, конкретная реализация еще должна быть определена) в базу данных;
2. Когда токен сохраняется, помните, что он генерируется случайным образом и после создания будет привязан к идентификатору входа пользователя;
3. Таким образом, после того, как мы войдем в систему, объект JSON, возвращаемый в браузер, должен содержать значение токена, и браузер сохранит значение токена в браузере.
Шаг 4: Создание и передача токена
После того, как идея ясна, мы должны реализовать:
1. Создайте токен:
package cn.lxt.controller;
import cn.lxt.bean.User;
import cn.lxt.service.TokenService;
import cn.lxt.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Controller
public class LoginController {
@Autowired
private UsersService usersService;
@Autowired
private TokenService tokenService;
//登录成功后返回一个map
@PostMapping(value = "/login")
@ResponseBody
public Map<String, Object> login(HttpServletRequest request){
String username = request.getParameter("name");
User user = usersService.findByName(username);
//给这个User创建一个token
Map<String,Object> map = tokenService.createToken(user.getId());
map.put("user",user);
return map;
}
}
Верните пользователя и токен на интерфейс в контроллере;
2. Создайте токен в Сервисе и сохраните его в базе данных:
package cn.lxt.service.Impl;
import cn.lxt.bean.Token;
import cn.lxt.dao.TokenMapper;
import cn.lxt.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
public class TokenServiceImple implements TokenService{
private final static int EXPIRE = 3600*12;
@Autowired
private TokenMapper tokenMapper;
@Override
//未完成功能:判断是否已存在,已存在覆盖掉
public Map createToken(int userId) {
Map<String,Object> map = new HashMap<String, Object>();
//设置一个随机产生的token
String token = UUID.randomUUID().toString();
//设置创建时间
Date createTime = new Date();
//设置过期时间 创建日期加12小时
Date expireTime = new Date(createTime.getTime()+EXPIRE*1000);
//判断token是否已存在
//这里先省略
Token tokenEntity = new Token(userId,token,expireTime,createTime);
//保存到数据库
tokenMapper.insert(tokenEntity);
System.out.println("存入成功");
map.put("token",token);
return map;
}
}
При создании токена выше не было определено, существует ли уже токен ID пользователя в базе данных по причинам времени, можете попробовать сами;
3. Хорошо, наш токен создан и пропущен через него в формате JSON, теперь осталось только сохранить токен в браузере:
На кнопке входа в интерфейс входа мы устанавливаем метод js:
function login() {
$.ajax({
url:"/login",
type:"post",
dataType:"JSON",
data:$(".login").serialize(),
success:function (map) {
if(map.token!=null){
localStorage.setItem("token",map.token);
window.location.href="/success";
}else{
alert("token为空");
}
}
})
}
Приведенный выше код передает токен в localStorage.
но, Внимательные студенты обнаружат, что в коде Realm, когда передается параметр (токен AuthenticationToken), он получает токен из внешнего заголовка, а наш токен хранится в localStorage, поэтому теперь нам нужно получить доступ к любому URL-адресу, вы можете перенести токен из localStorage в шапку, эта проблема оставлена вам, остроумным, если вы действительно не можете это сделать, вы можете написать мне лично (следуйте за мной, и я отвечу быстрее, правда)
Выше приведена простая реализация аутентификации при входе в систему безопасности Shiro в Spring Boot;
Если вы считаете, что это нормально, пожалуйста, поставьте лайк, если вам это не нравится, вы также можете добавить его в закладки;
В любом случае спасибо за прочтение~