SSM 权限管理整合:Spring Security 或 Shiro 与 SSM 的集成方案

SSM 权限管理整合:Spring Security 或 Shiro 与 SSM 的集成方案 – 拯救你那混乱不堪的权限系统!

各位码农朋友们,大家好!今天咱们要聊聊一个让大家头疼,却又不得不面对的问题:权限管理。

你是不是也经历过这样的噩梦?

  • 混乱的代码: 权限控制逻辑散落在代码的各个角落,像一堆乱麻,改动起来让人抓狂。
  • 脆弱的安全: 稍微不注意,就可能出现权限漏洞,让你的系统暴露在风险之中。
  • 重复的劳动: 每个项目都要重新写一套权限控制逻辑,简直是浪费生命。

别担心,你不是一个人!今天,我就来拯救你那混乱不堪的权限系统,带你一起探索如何将 Spring Security 或 Shiro 与 SSM (Spring + SpringMVC + MyBatis) 框架完美集成,打造一套安全、高效、可维护的权限管理方案。

为什么要集成权限管理框架?

在深入技术细节之前,我们先来聊聊为什么要集成权限管理框架。难道自己写一套权限控制逻辑不好吗?

当然不好!

  • 专业的事情交给专业的人做: Spring Security 和 Shiro 都是久经考验的权限管理框架,它们已经帮你处理了各种复杂的安全问题,比如身份认证、授权、会话管理等等。你只需要专注于业务逻辑,而不用操心底层的安全细节。
  • 提高开发效率: 使用权限管理框架可以大大简化权限控制的开发工作,减少重复代码,提高开发效率。
  • 增强系统安全性: 权限管理框架提供了各种安全机制,比如防止 CSRF 攻击、XSS 攻击等等,可以有效增强系统的安全性。
  • 提高代码可维护性: 权限管理框架将权限控制逻辑从业务代码中分离出来,使得代码更加清晰、易于维护。

简单来说,选择 Spring Security 或 Shiro,就相当于站在了巨人的肩膀上,可以让你更快、更安全地构建你的应用。

Spring Security vs. Shiro:选哪个?

Spring Security 和 Shiro 都是优秀的权限管理框架,它们各有优缺点。选择哪个框架取决于你的具体需求和偏好。

特性 Spring Security Shiro
出身 Spring 家族,与 Spring 框架无缝集成 Apache 基金会,独立于 Spring 框架
学习曲线 相对陡峭,配置比较繁琐,需要对 Spring 框架有深入的了解 相对平缓,配置简单,易于上手
灵活性 非常灵活,可以高度定制,满足各种复杂的权限需求 比较灵活,但不如 Spring Security
功能 功能强大,支持身份认证、授权、会话管理、防止 CSRF 攻击、XSS 攻击等等 功能完善,支持身份认证、授权、会话管理、加密等等
生态系统 依赖 Spring 生态系统,可以方便地与其他 Spring 组件集成 生态系统相对较小,但仍然可以与其他框架集成
使用场景 适合大型、复杂的应用,需要高度定制的权限管理方案,以及对 Spring 框架有深入了解的团队 适合中小型应用,需要快速上手、简单配置的权限管理方案
配置方式 主要通过 XML 或 Java Config 配置 主要通过 INI 文件或 Java Config 配置

总而言之,如果你对 Spring 框架非常熟悉,并且需要高度定制的权限管理方案,那么 Spring Security 是一个不错的选择。如果你希望快速上手,并且需要一个简单易用的权限管理框架,那么 Shiro 可能更适合你。

接下来,我们将分别介绍如何将 Spring Security 和 Shiro 与 SSM 框架集成。

Spring Security 与 SSM 集成

1. 添加依赖

首先,我们需要在 pom.xml 文件中添加 Spring Security 的依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>${spring.security.version}</version>
</dependency>

其中 ${spring.security.version} 需要替换成你使用的 Spring Security 的版本号。

2. 配置 Spring Security

我们需要创建一个 Spring Security 的配置类,用于配置身份认证、授权规则等等。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll() // 允许匿名访问
                .antMatchers("/admin/**").hasRole("ADMIN") // 需要 ADMIN 角色才能访问
                .antMatchers("/user/**").hasRole("USER") // 需要 USER 角色才能访问
                .anyRequest().authenticated() // 其他请求需要认证
                .and()
            .formLogin()
                .loginPage("/login") // 自定义登录页面
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

这个配置类做了以下几件事:

  • 使用 @EnableWebSecurity 注解启用 Spring Security。
  • 重写 configure(HttpSecurity http) 方法,配置 HTTP 请求的安全规则。
    • antMatchers("/public/**").permitAll():允许匿名访问 /public/** 路径下的资源。
    • antMatchers("/admin/**").hasRole("ADMIN"):需要 ADMIN 角色才能访问 /admin/** 路径下的资源。
    • antMatchers("/user/**").hasRole("USER"):需要 USER 角色才能访问 /user/** 路径下的资源。
    • anyRequest().authenticated():其他请求需要认证。
    • formLogin().loginPage("/login").permitAll():使用表单登录,并指定自定义登录页面为 /login
    • logout().permitAll():允许用户注销。
  • 重写 configure(AuthenticationManagerBuilder auth) 方法,配置身份认证管理器。
    • auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()):使用 UserDetailsService 接口获取用户信息,并使用 PasswordEncoder 接口对密码进行加密。
  • 创建一个 PasswordEncoder Bean,用于对密码进行加密。

3. 实现 UserDetailsService 接口

UserDetailsService 接口用于从数据库或其他数据源获取用户信息。我们需要实现这个接口,并将其注入到 Spring Security 的配置中。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库或其他数据源获取用户信息
        if ("admin".equals(username)) {
            // 这里为了演示,直接写死用户信息
            return new User(username, passwordEncoder.encode("123456"), new ArrayList<>());
        } else {
            throw new UsernameNotFoundException("User not found: " + username);
        }
    }
}

这个实现类做了以下几件事:

  • 从数据库或其他数据源获取用户信息。
  • 如果用户存在,则创建一个 UserDetails 对象,并返回。
  • 如果用户不存在,则抛出一个 UsernameNotFoundException 异常。

4. 创建登录页面

我们需要创建一个登录页面,用于让用户输入用户名和密码进行登录。

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form action="/login" method="post">
        <div>
            <label>Username:</label>
            <input type="text" name="username" value="admin"/>
        </div>
        <div>
            <label>Password:</label>
            <input type="password" name="password" value="123456"/>
        </div>
        <div>
            <input type="submit" value="Login"/>
        </div>
    </form>
</body>
</html>

这个登录页面非常简单,只有一个用户名输入框、一个密码输入框和一个提交按钮。

5. 测试

启动你的 SSM 应用,访问 /admin/user 路径,你会发现 Spring Security 会自动将你重定向到登录页面。输入用户名和密码进行登录,如果认证成功,你就可以访问 /admin/user 路径下的资源了。

Shiro 与 SSM 集成

1. 添加依赖

首先,我们需要在 pom.xml 文件中添加 Shiro 的依赖:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>

其中 ${shiro.version} 需要替换成你使用的 Shiro 的版本号。

2. 配置 Shiro

我们需要创建一个 Shiro 的配置类,用于配置 Realm、安全管理器等等。

import org.apache.shiro.realm.Realm;
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 java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean
    public Realm myRealm() {
        return new MyRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login"); // 设置登录页面
        shiroFilterFactoryBean.setSuccessUrl("/index"); // 设置登录成功后跳转的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); // 设置未授权页面

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/public/**", "anon"); // 允许匿名访问
        filterChainDefinitionMap.put("/admin/**", "roles[admin]"); // 需要 admin 角色才能访问
        filterChainDefinitionMap.put("/user/**", "roles[user]"); // 需要 user 角色才能访问
        filterChainDefinitionMap.put("/**", "authc"); // 其他请求需要认证

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
}

这个配置类做了以下几件事:

  • 创建一个 Realm Bean,用于从数据库或其他数据源获取用户信息。
  • 创建一个 DefaultWebSecurityManager Bean,用于管理 Realm。
  • 创建一个 ShiroFilterFactoryBean Bean,用于配置 Shiro 过滤器。
    • setLoginUrl("/login"):设置登录页面为 /login
    • setSuccessUrl("/index"):设置登录成功后跳转的页面为 /index
    • setUnauthorizedUrl("/unauthorized"):设置未授权页面为 /unauthorized
    • filterChainDefinitionMap.put("/public/**", "anon"):允许匿名访问 /public/** 路径下的资源。
    • filterChainDefinitionMap.put("/admin/**", "roles[admin]"):需要 admin 角色才能访问 /admin/** 路径下的资源。
    • filterChainDefinitionMap.put("/user/**", "roles[user]"):需要 user 角色才能访问 /user/** 路径下的资源。
    • filterChainDefinitionMap.put("/**", "authc"):其他请求需要认证。

3. 实现 Realm 接口

Realm 接口用于从数据库或其他数据源获取用户信息和授权信息。我们需要实现这个接口,并将其注入到 Shiro 的配置中。

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 java.util.HashSet;
import java.util.Set;

public class MyRealm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();

        // 从数据库或其他数据源获取用户的角色和权限
        Set<String> roles = new HashSet<>();
        if ("admin".equals(username)) {
            roles.add("admin");
        } else if ("user".equals(username)) {
            roles.add("user");
        }

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(roles);
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();

        // 从数据库或其他数据源获取用户信息
        if ("admin".equals(username)) {
            // 这里为了演示,直接写死用户信息
            return new SimpleAuthenticationInfo(username, "123456", getName());
        } else if ("user".equals(username)) {
            return new SimpleAuthenticationInfo(username, "123456", getName());
        } else {
            throw new UnknownAccountException();
        }
    }
}

这个实现类做了以下几件事:

  • 重写 doGetAuthorizationInfo(PrincipalCollection principals) 方法,从数据库或其他数据源获取用户的角色和权限,并返回一个 AuthorizationInfo 对象。
  • 重写 doGetAuthenticationInfo(AuthenticationToken token) 方法,从数据库或其他数据源获取用户信息,并返回一个 AuthenticationInfo 对象。

4. 创建登录页面

我们需要创建一个登录页面,用于让用户输入用户名和密码进行登录。

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form action="/login" method="post">
        <div>
            <label>Username:</label>
            <input type="text" name="username" value="admin"/>
        </div>
        <div>
            <label>Password:</label>
            <input type="password" name="password" value="123456"/>
        </div>
        <div>
            <input type="submit" value="Login"/>
        </div>
    </form>
</body>
</html>

这个登录页面非常简单,只有一个用户名输入框、一个密码输入框和一个提交按钮。

5. 测试

启动你的 SSM 应用,访问 /admin/user 路径,你会发现 Shiro 会自动将你重定向到登录页面。输入用户名和密码进行登录,如果认证成功,你就可以访问 /admin/user 路径下的资源了。

总结

今天,我们一起学习了如何将 Spring Security 和 Shiro 与 SSM 框架集成,打造一套安全、高效、可维护的权限管理方案。

无论你选择 Spring Security 还是 Shiro,都可以帮助你解决权限管理的问题,让你的代码更加清晰、易于维护,让你的系统更加安全可靠。

希望这篇文章能够帮助你摆脱权限管理的噩梦,让你的开发工作更加轻松愉快!

记住,权限管理不是一件容易的事情,需要不断学习和实践才能掌握。希望大家能够坚持学习,不断提升自己的技术水平,成为一名优秀的开发者!

最后,祝大家编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注