安全框架 Shiro & Spring Security

一、什么是Shiro?

Apache Shiro 是一个Java 的安全(权限)框架。Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。

二、Shiro的主要功能

在这里插入图片描述
Authentication:身份认证、登录,验证用户是不是拥有相应的身份。

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作。

Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;

Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率

Concurrency:Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限自动的传播过去

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

三、Shiro的整体架构

外部来看Shiro,即从应用程序角度来观察如何使用shiro完成工作:
在这里插入图片描述
Shiro的主要架构就是上面圈起来的三个模块,Subject、SecurityManager、Realm

Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject,Subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,也可以是第三方进程、后台帐户、网络爬虫、机器人等。与Subject的所有交互都会委托给SecurityManager;Subject其实是一个门面,SecurityManageer 才是实际的执行者

SecurityManager:安全管理器,即所有与安全有关的操作都会与SercurityManager交互,并且它管理着所有的Subject,且负责进行认证,授权,会话,及缓存的管理。可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色,是Shiro的心脏

Realm:Shiro从Realm获取安全数据(如用户,角色,权限),也就是说SecurityManager要验证用户的数据和权限是否正确,那需要先从Realm中先读取到该用户的数据,然后才能验证。简单点来说,其实Realm就是用来读取用户数据的,可以把它当成数据库,从realm中读取用户信息,也就是从数据库中查用户信息,用户的权限,角色等。

Shiro的特点就是层次分明的很清晰,所以学习和用起来就很方便易于上手。

四、SpringBoot中集成Shiro

  1. 导入依赖,主要就是shiro_spring整合的那个包
	<!-- 导入shiro-spring整合包 -->
	<dependency>
	    <groupId>org.apache.shiro</groupId>
	    <artifactId>shiro-spring</artifactId>
	    <version>1.7.1</version>
	</dependency>
	
	<!--thymeleaf-shiro整合包-->
	<dependency>
	    <groupId>com.github.theborakompanioni</groupId>
	    <artifactId>thymeleaf-extras-shiro</artifactId>
	    <version>2.0.0</version>
	</dependency>
	
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-test</artifactId>
	    <scope>test</scope>
	</dependency>
  1. 编写配置,首先需要创建一个Realm对象,因为Shiro需要通过Realm来获取用户信息,继承AuthorizingRealm并重写两个方法,授权认证,安全框架中最主要的也就是授权和认证
public class UserRealm extends AuthorizingRealm{

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了---》授权逻辑:PrincipalCollection");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        Subject subject = SecurityUtils.getSubject();// 获取subject对象
        User currentUser = (User)subject.getPrincipal();// 获取认证时传过来的user对象

        info.addStringPermissions(currentUser.getPerms());// 设置权限

        // 用户登录后,把用户信息放到session
        subject.getSession().setAttribute("loginUser",currentUser);

        return info;
    }


    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了---》认证逻辑:AuthenticationToken");

        // 1. 判断用户名
        UsernamePasswordToken userToken= (UsernamePasswordToken) token;

        User user = new User();
        user.setUsername(userToken.getUsername());
        // 这里模拟用数据库,根据用户传来的用户名去数据库查询对应的user信息
        user=queryUserByUserName(user);
        if(user==null){// 用户名不存在
            return null;// 返回null, shiro底层就会输出UnknownAccountException,然后被我们的controller捕获到这个异常,就知道就是用户名不存在了
        }



        // 2. 验证密码  返回一个AuthenticationInfo的实现类
        // shiro会自动帮我们验证!
        // 第一个参数,放入user,那么上面的授权代码中就可以获取到该user信息,也就获取到该user对应的权限了
        // 重点是第二个参数就是从该用户名对应的   数据库中的真实的密码  !
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }

    // 为了省事,这里模拟从数据库查询的用户信息的方法
    public User queryUserByUserName(User user){
        Set set=new HashSet();

        if(user.getUsername().equals("root")){
            user.setPassword("123456");
            set.add("user:add");
            set.add("user:update");
            user.setPerms(set);
        }else if(user.getUsername().equals("admin")){
            user.setPassword("123456");
            set.add("user:add");
            user.setPerms(set);
        }else{
            user=null;
        }
        return user;
    }
}
  1. 编写Shiro的配置类
// 这里主要就是配置shiro的三个对象bean,三个bean都是互相关联,层层嵌套的
@Configuration
public class ShiroConfig {

    //创建 ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        /* 添加Shiro内置过滤器,常用的有如下过滤器:
            anon: 无需认证就可以访问
            authc: 必须认证才可以访问
            user: 如果使用了记住我功能就可以直接访问
            perms: 拥有某个资源权限才可以访问
            role: 拥有某个角色权限才可以访问
        */
        Map<String, String> filterMap = new LinkedHashMap<String, String>();
        // 设置页面对应权限,有该权限才可访问,注意各种权限的配置的顺序
        filterMap.put("/user/add", "perms[user:add]");
        filterMap.put("/user/update", "perms[user:update]");

        //filterMap.put("/user/*","authc");//authc 登录就可以访问,可使用通配符

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //设置登录页面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        // 设置未授权展示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");

        return shiroFilterFactoryBean;
    }


    // 创建 DefaultWebSecurityManager
    @Bean(name = "securityManager")// 指定bean的名称,不指定默认就是方法名
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置Realm对象
        securityManager.setRealm(userRealm);
        return securityManager;
    }


    // 创建 realm 对象
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    //配置ShiroDialect:方言,用于 thymeleaf 和 shiro 标签配合使用
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

}
  1. 控制层Controller , 主要看login方法
// 登录页面
    @RequestMapping({"/toLogin"})
    public String toLogin(){
        return "login";
    }

    // 点击登录,验证逻辑
    @RequestMapping({"/login"})
    public String login(String username, String password, Model model){

        // shiro的认证逻辑,开始了! (也就是用户名和密码的校验逻辑)


        //1. 获取subject
        Subject subject = SecurityUtils.getSubject();
        //2. 封装用户的数据(就是把页面传过来的用户名和密码进行封装,封装为一个token,用这个token去走验证逻辑)
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        //3. 执行登录方法
        try {
            subject.login(token);// 这个login方法很重要,它会去走UserRealm中的认证逻辑

            return "index"; //不报异常,说明认证成功!跳转到首页
        } catch (UnknownAccountException e) {
            model.addAttribute("msg","用户名不存在");
            return "login";
        } catch (IncorrectCredentialsException e) {
            model.addAttribute("msg","密码错误");
            return "login";
        }


    }

    @RequestMapping("/noAuth")
    @ResponseBody
    public String noAuth(){
        return "未经授权不能访问该页面";
    }
  1. 由于该示例用的是thymeleaf,所以导入了thymeleaf和shiro的整合包,就可以在模板中使用shiro的权限来控制一些菜单的展示和隐藏了
    使用前提,shiro的配置类中要注入一个bean
 //配置ShiroDialect:方言,用于 thymeleaf 和 shiro 标签配合使用
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-shiro" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

	<h1>首页</h1>
	<p th:text="${msg}"></p>
	
	<p th:if="${session.loginUser==null}">
	    <a th:href="@{/toLogin}">登录</a>
	</p>
	
	使用shiro的权限标签来控制菜单的显示与隐藏
	<div shiro:hasPermission="user:add">
	    <a th:href="@{/user/add}">add</a>
	</div>
	|
	<div shiro:hasPermission="user:update">
	    <a th:href="@{/user/update}">update</a>
	</div>

</body>
</html>

以上案例只是Shiro的一个简单实现,Shiro中的东西远不止这些,实际开发中的业务逻辑是很复杂的。

五、什么是Spring Security?

Spring Security是一个基于Spring框架的功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

安全框架一般来说要解决的最大的问题就是认证和授权。针对这两种情况,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。(相较于Shiro,Spring Security学习起来会稍微难一点,当然这是个人观点了)

六、SpringBoot中集成Spring Security

  1. 导入依赖
	<!--导入Spring Security依赖-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>
       <!--thymeleaf-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	  <!--thymeleaf与security整合包-->
	<dependency>
		<groupId>org.thymeleaf.extras</groupId>
		<artifactId>thymeleaf-extras-springsecurity5</artifactId>
	</dependency>
	<!--springboot web 依赖-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
     
  1. 建立一个Controller,和对应的访问页面
@Controller
public class RouterController {


    @RequestMapping({"/", "/index"})
    public String index() {
        return "index";
    }

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

    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id) {
        return "views/level1/" + id;
    }

    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id) {
        return "views/level2/" + id;
    }

    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id) {
        return "views/level3/" + id;
    }
}

在这里插入图片描述

  1. 编写spring security配置文件
@EnableWebSecurity // 开启Security功能
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    // 权限
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");

        // 下面这是最简单登录和注销的配置,使用的都是security默认的登录和注销页面
        //http.formLogin();//配置登录  默认登录页面/login
        //http.logout();// 配置注销   默认注销页面/logout

        /*http.formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginPage("/toLogin") // loginPage 定制登录页面
                .loginProcessingUrl("/login");// loginProcessingUrl 用户名密码校验逻辑路径*/

        http.formLogin().loginPage("/toLogin");// 若不配置loginProcessingUrl,则loginProcessingUrl默认和loginPage路径一样

        http.csrf().disable();//关闭csrf:跨站请求伪造,不关闭的话默认只能通过post请求来注销,而我们页面上的注销按钮就是一个get请求的/logout跳转路径
        http.logout().logoutSuccessUrl("/");//设置注销后跳转的页面

        //记住我,就是保持登录状态,浏览器关闭再打开仍保持登录状态
        http.rememberMe().rememberMeParameter("rememberCheck");

    }

    // 认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // 账号和密码应该从数据库查,这里为了简单示例就直接把账号密码写到内存里
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
                .and()
                .withUser("hhl").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3");
    }
}

注意的事项:

  1. spring security 有默认的登录页面和注销页面
  2. 认证逻辑方法中的密码必须加密传输
  3. loginProcessingUrl是认证的逻辑判断路径,默认是/login
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值