Система фонового управления на основе SpringBoot (Apache Shiro, Spring Session (фокус)) (5)

Spring Apache Безопасность Shiro

5. Управление разрешениями Широ, Spring Session, XssFilter

инструкция

Если вам посчастливилось увидеть это, пожалуйста, прочитайте следующее содержание;

  • 1. Этот проект скопирован сabel533Guns, форк его проекта с тех порstylefengизGuns! Мир открытого исходного кода велик, и в нем есть чему поучиться.

  • 2. Авторские права принадлежат оригинальному автору, и я использую их только для обучения. Следуйте идеям босса и надейтесь, что вы тоже сможете стать боссом. гогого». .

  • 3. В настоящее время это только фоновый модуль. Я надеюсь, что когда мои навыки будут в определенной степени улучшены, я смогу поставитьstylefeng[Пистолеты] сливаются.

  • 4. Внутри заметки ваш собственный процесс обучения, написанный новичком, а не большим парнем. Содержание все мастера.

  • 5. Пожалуйста, простите меня за орфографические ошибки. Текущая таблица не подходит для набора текста, эта статья просто для моей собственной записи.

Позвольте задать вам вопрос: когда вы читаете эту статью, считаете ли вы, что есть что-то, что нужно пересмотреть? Что касается содержания и формата, каждый может представить его.

содержание

  • 1. Первая остановка SpringBoot: анализ класса запуска. Существуют также различные исходные коды автоматической настройки.кликните сюда
  • 2. Вторая остановка SpringBoot: определяет исключения, аннотации, узлы Node, Pageкликните сюда
  • 3. Третья остановка SpringBoot: конфигурация источника данных SpringBoot, конфигурация Mybatis, ведение журналакликните сюда
  • 4. Четвертая остановка SpringBoot: конфигурация кеша SpringBoot, глобальная обработка исключенийкликните сюда

инструкция

Если вам посчастливилось увидеть это, пожалуйста, прочитайте следующее содержание;

  • 1. Этот проект скопирован сabel533Guns, форк его проекта с тех порstylefengизGuns! Мир открытого исходного кода велик, и в нем есть чему поучиться.

  • 2. Авторские права принадлежат оригинальному автору, и я использую их только для обучения. Следуйте идеям босса и надейтесь, что вы тоже сможете стать боссом. гогого». .

  • 3. В настоящее время это только фоновый модуль. Я надеюсь, что когда мои навыки будут в определенной степени улучшены, я смогу поставитьstylefeng[Пистолеты] сливаются.

  • 4. Внутри заметки ваш собственный процесс обучения, написанный новичком, а не большим парнем. Содержание все мастера.

Apache Shiro

В этом проекте для сопровождения безопасности используется Apache's Shiro.На самом деле, Spring также предоставляет фреймворк для декларативной защиты безопасности, то есть Spring Securitu. Если вам интересно, вы можете прочитать мои предыдущие заметкиБоевые заметки Spring-Security.

(1), Spring Security — это инфраструктура безопасности, основанная на декларативной защите безопасности, обеспечиваемой приложениями Spring. Spring Sercurity предоставляет комплексное решение для обеспечения безопасности, которое обрабатывает аутентификацию и авторизацию на уровне веб-запросов и уровне вызова методов.Поскольку Spring Security основан на Spring, в полной мере использует внедрение зависимостей (DI) и аспектно-ориентированные технологии.

Spring Security рассматривает безопасность с двух точек зрения: он использует фильтр в спецификации сервлета для защиты веб-запросов и ограничения доступа на уровне URL-адресов. Spring Security также может использовать АОП для защиты вызовов методов — с помощью проксирования объектов и рекомендаций по использованию можно гарантировать, что только пользователи с соответствующими привилегиями могут получить доступ к защищенным методам.

(2), Apache Shiro — это мощная и гибкая система безопасности с открытым исходным кодом, основные функции которой включают аутентификацию пользователей, авторизацию, управление сеансами и шифрование.

Основная цель Apache Shiro — сделать его простым в использовании и понятным. Безопасность системы очень сложна и даже болезненна, но Широ нет. Фреймворк должен максимально скрывать эти сложные детали и предоставлять набор кратких и интуитивно понятных API, чтобы упростить усилия разработчика по обеспечению безопасности системы.

Лично склоняюсь к первому, ведь он является членом экосистемы Spring.

Особенности Apache Широ:

  • Аутентифицировать пользователя
  • Контролировать доступ пользователей
  • Оперативно реагируйте на все события в рамках цикла аутентификации, управления доступом или подтверждения сеанса.
  • Реализовать функцию единого входа

Особенности Apache Широ:обратитесь сюда

Эти функции — то, что команда разработчиков Shiro называет «четырьмя краеугольными камнями безопасности приложений» — аутентификация, авторизация, управление сеансами и шифрование:

  • Аутентификация: Иногда ее называют «логином», то есть проверкой того, кем является пользователь.
  • Авторизация: управляет контролем доступа, например, решает, «кто» может получить доступ к «каким» ресурсам.
  • Управление сеансами: управляйте сеансами для определенных пользователей, даже в не-веб-средах или средах приложений, отличных от EJB.
  • Шифрование: используйте алгоритмы шифрования для обеспечения безопасности данных при сохранении простоты использования.
  • Веб-поддержка: веб-API Широ может очень удобно повысить безопасность веб-приложений.
  • Кэширование. Кэширование обеспечивает быстроту и эффективность API-интерфейсов Apache Shiro для обеспечения безопасности операций.

Прочитав эти основные понятия, давайте посмотрим непосредственно на определение интерфейса.

/**
 * 定义shirorealm所需数据的接口
 */
public interface IShiro {

    /**
     * 根据账号获取登录用户
     */
    User user(String account);

    /**
     * 根据系统用户获取Shiro的用户
     */
    ShiroUser shiroUser(User user);

    //省略部分
    /**
     * 获取shiro的认证信息
     */
    SimpleAuthenticationInfo info(ShiroUser shiroUser, User user, String realmName);
}

--------------------------------------------------------------------------------
/**
 * 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息
 */
public class ShiroUser implements Serializable {

    private static final long serialVersionUID = 1L;

    public Integer id;          // 主键ID
    public String account;      // 账号
    public String name;         // 姓名
    public Integer deptId;      // 部门id
    public List<Integer> roleList; // 角色集
    public String deptName;        // 部门名称
    public List<String> roleNames; // 角色名称集
    //Setter、Getter略

завод ШироФактрой,SpringContextHolderОн является держателем ApplicationContext Spring и может использовать статические методы для получения bean-компонентов в контейнере Spring.

@Service
@DependsOn("springContextHolder")
@Transactional(readOnly = true)
public class ShiroFactroy implements IShiro {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MenuMapper menuMapper;

    public static IShiro me() {
        return SpringContextHolder.getBean(IShiro.class);
    }

    @Override
    public User user(String account) {

        User user = userMapper.getByAccount(account);

        // 账号不存在
        if (null == user) {
            throw new CredentialsException();
        }
        // 账号被冻结
        if (user.getStatus() != ManagerStatus.OK.getCode()) {
            throw new LockedAccountException();
        }
        return user;
    }

    @Override
    public ShiroUser shiroUser(User user) {
        ShiroUser shiroUser = new ShiroUser();

        shiroUser.setId(user.getId());            // 账号id
        shiroUser.setAccount(user.getAccount());// 账号
        shiroUser.setDeptId(user.getDeptid());    // 部门id
        shiroUser.setDeptName(ConstantFactory.me().getDeptName(user.getDeptid()));// 部门名称
        shiroUser.setName(user.getName());        // 用户名称

        Integer[] roleArray = Convert.toIntArray(user.getRoleid());// 角色集合
        List<Integer> roleList = new ArrayList<Integer>();
        List<String> roleNameList = new ArrayList<String>();
        for (int roleId : roleArray) {
            roleList.add(roleId);
            roleNameList.add(ConstantFactory.me().getSingleRoleName(roleId));
        }
        shiroUser.setRoleList(roleList);
        shiroUser.setRoleNames(roleNameList);

        return shiroUser;
    }

  //省略部分

    @Override
    public SimpleAuthenticationInfo info(ShiroUser shiroUser, User user, String realmName) {
        String credentials = user.getPassword();
        // 密码加盐处理
        String source = user.getSalt();
        ByteSource credentialsSalt = new Md5Hash(source);
        return new SimpleAuthenticationInfo(shiroUser, credentials, credentialsSalt, realmName);
    }

}

Этот класс является точкой

public class ShiroDbRealm extends AuthorizingRealm {

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        IShiro shiroFactory = ShiroFactroy.me();
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        User user = shiroFactory.user(token.getUsername());
        ShiroUser shiroUser = shiroFactory.shiroUser(user);
        SimpleAuthenticationInfo info = shiroFactory.info(shiroUser, user, super.getName());
        return info;
    }
-------------------------------------------------------------------------------------------------
    /**
     * 权限认证
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        IShiro shiroFactory = ShiroFactroy.me();
        ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
        List<Integer> roleList = shiroUser.getRoleList();

        Set<String> permissionSet = new HashSet<>();
        Set<String> roleNameSet = new HashSet<>();

        for (Integer roleId : roleList) {
            List<String> permissions = shiroFactory.findPermissionsByRoleId(roleId);
            if (permissions != null) {
                for (String permission : permissions) {
                    if (ToolUtil.isNotEmpty(permission)) {
                        permissionSet.add(permission);
                    }
                }
            }
            String roleName = shiroFactory.findRoleNameByRoleId(roleId);
            roleNameSet.add(roleName);
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionSet);
        info.addRoles(roleNameSet);
        return info;
    }
-------------------------------------------------------------------------------------
    /**
     * 设置认证加密方式
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
        md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
        md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
        super.setCredentialsMatcher(md5CredentialsMatcher);
    }
}

Давайте посмотрим, как его использовать? Разрешения установлены, но вам нужно проверять, есть ли у вас разрешения каждый раз, когда вы их используете


/**
 *  检查用接口
 */
public interface ICheck {

    /**
     * 检查指定角色
     */
    boolean check(Object[] permissions);

    /**
     * 检查全体角色
     */
    boolean checkAll();
}
--------------------------------------------------------------------------------
/**
 * 权限自定义检查
 */
@Service
@DependsOn("springContextHolder")
@Transactional(readOnly = true)
public class PermissionCheckFactory implements ICheck {

    public static ICheck me() {
        return SpringContextHolder.getBean(ICheck.class);
    }

    @Override
    public boolean check(Object[] permissions) {
        ShiroUser user = ShiroKit.getUser();
        if (null == user) {
            return false;
        }
        String join = CollectionKit.join(permissions, ",");
        if (ShiroKit.hasAnyRoles(join)) {
            return true;
        }
        return false;
    }
    public boolean checkAll() {...}
}
/**
 * 权限检查工厂
 */
public class PermissionCheckManager {
    private final static PermissionCheckManager me = new PermissionCheckManager();

    private ICheck defaultCheckFactory = SpringContextHolder.getBean(ICheck.class);

    public static PermissionCheckManager me() {
        return me;
    }
    //....
    public static boolean check(Object[] permissions) {
        return me.defaultCheckFactory.check(permissions);
    }

    public static boolean checkAll() {
        return me.defaultCheckFactory.checkAll();
    }
}

В это время вам нужно установить разрешение как аспект и напрямую вплести его, когда это необходимо.

/**
 * 权限注解,用于检查权限 规定访问权限
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Permission {
    String[] value() default {};
}
-------------------------------------------------------------------------------
/**
 * AOP 权限自定义检查
 */
@Aspect
@Component
public class PermissionAop {

    @Pointcut(value = "@annotation(com.guo.guns.common.annotion.Permission)")
    private void cutPermission() {

    }

    @Around("cutPermission()")
    public Object doPermission(ProceedingJoinPoint point) throws Throwable {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Permission permission = method.getAnnotation(Permission.class);
        Object[] permissions = permission.value();
        if (permissions == null || permissions.length == 0) {
            //检查全体角色
            boolean result = PermissionCheckManager.checkAll();
            if (result) {
                return point.proceed();
            } else {
                throw new NoPermissionException();
            }
        } else {
            //检查指定角色
            boolean result = PermissionCheckManager.check(permissions);
            if (result) {
                return point.proceed();
            } else {
                throw new NoPermissionException();
            }
        }
    }
}

Давайте посмотрим, как это используется в коде. Только администраторы имеют право на изменение.

/**
 * 管理员角色的名字
 */
String ADMIN_NAME = "administrator";
--------------------------------------------------------------------------------
/**
 * 角色修改
 */
@RequestMapping(value = "/edit")
@BussinessLog(value = "修改角色", key = "name", dict = Dict.RoleDict)
@Permission(Const.ADMIN_NAME)
@ResponseBody
public Tip edit(@Valid Role role, BindingResult result) {
    if (result.hasErrors()) {
        throw new BussinessException(BizExceptionEnum.REQUEST_NULL);
    }
    roleMapper.updateByPrimaryKeySelective(role);

    //删除缓存
    CacheKit.removeAll(Cache.CONSTANT);
    return SUCCESS_TIP;
}

Spring Session

Предыдущий подход к работе с сеансами заключался в сохранении состояния сеанса HTTP в отдельном хранилище данных за пределами JVM, где выполнялся код приложения. Используя проект с открытым исходным кодом tomcat-redis-session-manager для решения проблемы распределенного междоменного сеанса, его основная идея состоит в том, чтобы использовать функцию подключаемого модуля, предоставляемую контейнером сервлета, для настройки стратегии создания и управления HttpSession и заменить значение по умолчанию на конфигурацию.Стратегия. Любой, кто использовал tomcat-redis-session-manager, должен знать, что конфигурация относительно громоздка.Необходимо вручную изменить конфигурацию Tomcat, и необходимо связать код контейнеров сервлетов, таких как Tomcat, и управление Распределенные кластеры Redis — это не так. С другой стороны, Spring Session, фреймворк, который я лично считаю лучше, действительно может управлять распределенными сеансами прозрачно для пользователей.Ссылаться на

Spring Session предоставляет решение для создания и управления Servlet HttpSessions. Spring Session предоставляет функцию кластерных сеансов.По умолчанию внешний Redis используется для хранения данных сеанса, чтобы решить проблему совместного использования сеанса.

Spring Session не зависит от контейнера Servlet, а реализуется на уровне кода веб-приложения, напрямую добавляя фреймворк Spring Session на основе существующих проектов для реализации унифицированного хранилища Session в Redis. Если ваше веб-приложение разработано на основе среды Spring, вам нужно лишь выполнить небольшую настройку существующего проекта, и вы можете изменить одномашинную версию веб-приложения на распределенное приложение. в контейнере Servlet вы можете по желанию портировать проект в другие контейнеры.

/**
 * spring session配置
 */
//@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)  //session过期时间  如果部署多机环境,需要打开注释
@ConditionalOnProperty(prefix = "guns", name = "spring-session-open", havingValue = "true")
public class SpringSessionConfig {


}

Простой в использовании, потому что он декларативен

/**
 * 静态调用session的拦截器
 */
@Aspect
@Component
public class SessionInterceptor extends BaseController {

    @Pointcut("execution(* com.guo.guns.*..controller.*.*(..))")
    public void cutService() {
    }

    @Around("cutService()")
    public Object sessionKit(ProceedingJoinPoint point) throws Throwable {

        HttpSessionHolder.put(super.getHttpServletRequest().getSession());
        try {
            return point.proceed();
        } finally {
            HttpSessionHolder.remove();
        }
    }
}
--------------------------------------------------------------------------------
/**
 * 验证session超时的拦截器
 *
 * @author fengshuonan
 * @date 2017年6月7日21:08:48
 */
@Aspect
@Component
@ConditionalOnProperty(prefix = "guns", name = "session-open", havingValue = "true")
public class SessionTimeoutInterceptor extends BaseController {

    @Pointcut("execution(* com.guo.guns.*..controller.*.*(..))")
    public void cutService() {
    }

    @Around("cutService()")
    public Object sessionTimeoutValidate(ProceedingJoinPoint point) throws Throwable {

        String servletPath = HttpKit.getRequest().getServletPath();

        if (servletPath.equals("/kaptcha") || servletPath.equals("/login") || servletPath.equals("/global/sessionError")) {
            return point.proceed();
        }else{
            if(ShiroKit.getSession().getAttribute("sessionFlag") == null){
                ShiroKit.getSubject().logout();
                throw new InvalidSessionException();
            }else{
                return point.proceed();
            }
        }
    }
}

Давайте посмотрим, как это использовать"

/**
 * 获取shiro指定的sessionKey
 *
 */
@SuppressWarnings("unchecked")
public static <T> T getSessionAttr(String key) {
    Session session = getSession();
    return session != null ? (T) session.getAttribute(key) : null;
}

/**
 * 设置shiro指定的sessionKey
 *
 */
public static void setSessionAttr(String key, Object value) {
    Session session = getSession();
    session.setAttribute(key, value);
}

/**
 * 移除shiro指定的sessionKey
 */
public static void removeSessionAttr(String key) {
    Session session = getSession();
    if (session != null)
        session.removeAttribute(key);
}
-------------------登录执行中的步骤-----------------------------------------
ShiroUser shiroUser = ShiroKit.getUser();
super.getSession().setAttribute("shiroUser", shiroUser);
super.getSession().setAttribute("username", shiroUser.getAccount());

LogManager.me().executeLog(LogTaskFactory.loginLog(shiroUser.getId(), getIp()));

ShiroKit.getSession().setAttribute("sessionFlag",true);

return REDIRECT + "/";

super.getSession() вызывает метод в BaseController.

Предотвращение XSS-атак

Для предотвращения XSS-атак все входные недопустимые строки фильтруются и заменяются классом XssFilter.

public class XssFilter implements Filter {

    FilterConfig filterConfig = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    public void destroy() {
        this.filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new XssHttpServletRequestWrapper(
                (HttpServletRequest) request), response);
    }

}
---------------------------------------------------------------------------------
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {

        super(servletRequest);

    }

    public String[] getParameterValues(String parameter) {

        String[] values = super.getParameterValues(parameter);

        if (values == null) {

            return null;

        }

        int count = values.length;

        String[] encodedValues = new String[count];

        for (int i = 0; i < count; i++) {

            encodedValues[i] = cleanXSS(values[i]);

        }

        return encodedValues;

    }
    //省略部分

    private String cleanXSS(String value) {

        //You'll need to remove the spaces from the html entities below

        value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");

        value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");

        value = value.replaceAll("'", "& #39;");

        value = value.replaceAll("eval\\((.*)\\)", "");

        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");

        value = value.replaceAll("script", "");

        return value;

    }
}

Просто один раз прошёл.Если хотите глубже понять, то всё равно надо потрудиться.Это просто ваши собственные записи.