SpringSecurityShiro初见

目录

  • [简介] 简介
  • [实战环境搭建] 实战环境搭建
  • [SpringSecurity] springsecurity
    • [认证和授权] 认证和授权
    • [权限控制和注销] 权限控制和注销
    • [记住] 记住
  • [Shiro] shiro
    • [快速上手] 快速上手
    • [shiro整合mybais] shiro整合mybais

简介

在 Web 开发中,安全一直是非常重要的一个方面。

安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。

市面上存在比较有名的:Shiro,Spring Security 

首先们看下它的官网介绍:Spring Security官网地址
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

从官网的介绍中可以知道这是一个权限框架
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,Spring Security 框架都有很好的支持。
在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,
Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

实战环境搭建

项目代码地址:https://gitee.com/zwtgit/spring-security-or-shiro
1、新建一个初始的springboot项目web模块,thymeleaf模块
2、导入静态资源,资源上面有
3、controller跳转!

@Controller
public class RouterController {
   @RequestMapping()
   public String index(){
       return "index";
  }
}

4、测试实验环境是否OK!

SpringSecurity


Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,

他可以实现强大的Web安全控制,对于安全控制,们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式
    Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
    "认证"(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
"授权" (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

认证和授权

1、引入 Spring Security 模块

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2、编写 Spring Security 配置类

查看们自己项目中的版本,找到对应的帮助文档:
https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 servlet-applications 8.16.4
3、编写基础配置类

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   @Override
   protected void configure(HttpSecurity http) throws Exception {

  }
}

4、定制请求的授权规则

@Override
protected void configure(HttpSecurity http) throws Exception {
   // 定制请求的授权规则
   // 首页所有人可以访问
   http.authorizeRequests().antMatchers("/").permitAll()
  .antMatchers("/level1/**").hasRole("vip1")
  .antMatchers("/level2/**").hasRole("vip2")
  .antMatchers("/level3/**").hasRole("vip3");
}

5、测试一下:发现除了首页都进不去了!因为们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!
6、在configure()方法中加入以下配置,开启自动配置的登录功能!

// 开启自动配置的登录功能
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin();

7、测试一下:发现,没有权限的时候,会跳转到登录的页面!
8、查看刚才登录页的注释信息;

们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法

//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

   //在内存中定义,也可以在jdbc中去拿....
   auth.inMemoryAuthentication()
          .withUser("kuangshen").password("123456").roles("vip2","vip3")
          .and()
          .withUser("root").password("123456").roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password("123456").roles("vip1","vip2");
}

9、测试,们可以使用这些账号登录进行测试!发现会报错!
There is no PasswordEncoder mapped for the id “null”
10、原因,们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码

//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   //在内存中定义,也可以在jdbc中去拿....
   //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
   //要想们的项目还能够正常登陆,需要修改一下configure中的代码。们要将前端传过来的密码进行某种方式加密
   //spring security 官方推荐的是使用bcrypt加密方式。

   auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
          .withUser("zwt").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
          .and()
          .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}

11、测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!

权限控制和注销

1、开启自动配置的注销的功能

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
   //....
   //开启自动配置的注销的功能
      // /logout 注销请求
   http.logout();
}

2、们在前端,增加一个注销的按钮,index.html 导航栏中
3、们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!
4、但是,们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?

// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");

5、测试,注销完毕后,发现跳转到首页OK
6、们现在又来一个需求:根据权限访问页面

们需要结合thymeleaf中的一些功能
sec:authorize=“isAuthenticated()”:是否认证登录!来显示不同的页面
Maven依赖:

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

7、修改们的 前端页面

  1. 导入命名空间
    8、重启测试,们可以登录试试看,登录成功后确实,显示了们想要的页面;
    9、如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;
http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.logout().logoutSuccessUrl("/");

权限控制和注销搞定!

记住

1、开启记住功能

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
   //记住
   http.rememberMe();
}

原理: spring security 登录成功后,将cookie发送给浏览器保存,

以后登录带上这个cookie,只要通过检查就可以免登录了。

如果点击注销,则会删除这个cookie,具体的原理在JavaWeb阶段都讲过了,这里就不在多说了!

Shiro

Apache Shiro是一个Java的安全(权限)框架。
Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
三大核心组件
Shiro有三大核心组件,即Subject、SecurityManager和Realm

|       组件        |                |                                                                                                              |
|-----------------|----------------|--------------------------------------------------------------------------------------------------------------|
| Subject         | 用户,认证主体        | 应用代码直接交互的对象是Subject,Subject。包含Principals和Credentials两个信息                                                     |
| SecurityManager | 管理所有用户,为安全管理员。 | 是Shiro架构的核心。与Subject的所有交互都会委托给SecurityManager, Subject相当于是一个门面,而SecurityManager才是真正的执行者。它负责与Shiro 的其他组件进行交互。 |
| Realm           | 连接数据,是一个域。     | 可以把Realm看成DataSource,即安全数据源。                                                                                 |

Pricipals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登陆主题的身份。

Credentials:代表凭证。常见的有密码、数字证书等等。

在官网的例子中了解Shiro

public class Quickstart {
    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
    public static void main(String[] args) {

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        // 获取当前用户
        Subject currentUser = SecurityUtils.getSubject();
        // 通过当前用户拿到session
        Session session = currentUser.getSession();
        //在session中存值
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }
        // 判断当前的用户是否被认证
        if (!currentUser.isAuthenticated()) {
        //token 令牌 随意设置
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            //设置记住
            token.setRememberMe(true);
            try {
                currentUser.login(token); //执行了登录操作
            } catch (UnknownAccountException uae) { //用户名不存在
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) { //密码错误
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) { //用户被锁定
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) { //认证异常
                //unexpected condition?  error?
            }
        }

        //获取当前用户的认证
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
        //获得当前用户的角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
            log.info("Hello, mere mortal.");
        }
        //是否拥有粗粒度(简单)权限
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }
        //是否拥有更高权限
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }
        //注销
        currentUser.logout();
        //结束
        System.exit(0);
    }
}

快速上手

导入依赖

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

编写shiro配置类

@Configuration
public class ShiroConfig {
    //shiroFilterBean 第三步
    //DefaultWebSecurityManager 第二步
    //创建Realm对象 需要自定义类 第一步
}

自定义Realm

//自定义Realm 需要继承AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行了授权=>");
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了认证");
        return null;
    }
}

继续编写shiro配置类

@Configuration
public class ShiroConfig {
    //ShiroFilterFactoryBean 第三步
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        //设置安全管理器
        filter.setSecurityManager(manager);
        return filter;
    }
    //DefaultWebSecurityManager 第二步
    @Bean(name="manager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        //关联UserRealm
        manager.setRealm(userRealm);
        return manager;
    }
    //创建Realm对象 需要自定义类 第一步
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

实现登录拦截

//ShiroFilterFactoryBean 第三步
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        //设置安全管理器
        filter.setSecurityManager(manager);
        //增加shiro内置过滤器
        /**
         * anon: 无需认证就可以访问
         * authc: 必须认证才能访问
         * user: 必须拥有记住功能才能访问
         * perms: 拥有对某个资源的权限才能访问
         * role:拥有某个角色权限才能访问
         */
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/user/add","authc");
        map.put("/user/update","authc");
        filter.setFilterChainDefinitionMap(map);
        //设置登录页面
        filter.setLoginUrl("/login");
        return filter;
    }

用户认证

   //用户登录功能
    @PostMapping("/tologin")
    public String tologin(String username, String password, Model model){
        //获取用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户登录数据并且生成一个token令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try{
            subject.login(token);//登录 如果没有异常就说明OK了
            return "index";//返回首页
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }



            //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了认证");
        //用户名密码  数据库中取
        String name="admin";
        String password="123";
        UsernamePasswordToken userName=(UsernamePasswordToken)token;
        if (!userName.getUsername().equals(name)){
            return null;//抛出异常
        }
        //密码认证 shiro做
        return new SimpleAuthenticationInfo("",password,"");
    }

shiro整合mybais

导入依赖

      <!--spring boot整合mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--Mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--SpringbootJDBC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

编写配置

spring:
  thymeleaf:
    cache: false 关闭模板引擎的缓存
   配置数据源 serverTimezone=UTC 设置时区
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  type-aliases-package: com.zwt.pojo
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

编写实体类,编写接口,编写映射文件

修改自定义Realm代码

 @Autowired
    private UserMapper userMapper;
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了认证");
        UsernamePasswordToken userName=(UsernamePasswordToken)token;
        //连接真实数据库
        User user = userMapper.queryUserbyName(userName.getUsername());
        if(user==null){ //没查出用户
            return null;
        }
        //密码认证 shiro做 密码加密
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }

请求授权

编写Shiro过滤器
1.设置访问/user/add路径时,需要[user:add权限
2.没有此权限访问时,会跳转指定路径

 //ShiroFilterFactoryBean 第三步
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        //设置安全管理器
        filter.setSecurityManager(manager);
        //增加shiro内置过滤器
        /**
         * anon: 无需认证就可以访问
         * authc: 必须认证才能访问
         * user: 必须拥有记住功能才能访问
         * perms: 拥有对某个资源的权限才能访问
         * role:拥有某个角色权限才能访问
         */
        Map<String, String> map = new LinkedHashMap<>();
       // map.put("/user/add","authc");
        map.put("/user/update","authc");
        map.put("/user/add","perms[user:add]"); //访问此路径需要user:add权限
        filter.setFilterChainDefinitionMap(map);
        //设置登录页面
        filter.setLoginUrl("/login");
        filter.setUnauthorizedUrl("/noperms");//无权限时执行这个请求
        return filter;
    }

授权:

   //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行了授权=>");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:add");//对每个用户进行授权
        //取出用户信息
        Subject subject = SecurityUtils.getSubject();
        User principal = (User)subject.getPrincipal();
        //将用户信息的权限信息 设置进去
        //动态设置权限 需要新增一些数据库表 如:权限表
        //info.addStringPermission(principal.getParms());
        return info;
    }

原文创作:ML李嘉图

原文链接:https://www.cnblogs.com/zwtblog/p/15181147.html

原文创作:ML李嘉图

文章列表

更多推荐

更多
  • Java开发常见错误100例-29数据和代码:数据就是数据,代码就是代码 29 数据和代码:数据就是数据,代码就是代码 今天,我来和你聊聊数据和代码的问题。 正如这一讲标题"数据就是数据方面的很多漏洞,都是源自把数据当成了代码来执行,也就是注入类问题,比如: 客户端提供给服务端的查询值,是一个数据,会
  • Java开发常见错误100例-33加餐3:定位应用问题,排错套路很重要 33 加餐3:定位应用问题,排错套路很重要 咱们这个课程已经更新 13 讲了,感谢各位同学一直在坚持学习,并在评论区留下了很多高质量,有的是分享自己曾经踩的坑,有的是对课后思考题的详细解答,还有的是提出了非常好的问题,进一步丰富了这
  • Java开发常见错误100例-36加餐6:这15年来,我是如何在工作中学习技术和英语的? 36 加餐6:这15年来,我是如何在工作中学习技术和英语的? 今天,我来和你聊聊如何在工作中,让自己成长得更快。 工作这些年来,经常会有同学来找我沟通学习和成长,他们的问题可以归结为沟通学习和成长,他们的问题可以归结为两个。 一
  • Java开发常见错误100例-16用好Java8的日期时间类,少踩一些“老三样”的坑 16 用好Java 8的日期时间类,少踩一些"老三样"的坑 今天,我来和你说说恼人的时间错乱问题。 在,使用 Date、Calender 和 SimpleDateFormat,来声明时间戳、使用日历处理日期和格式化解析日期时间。但
  • Java开发常见错误100例-答疑篇:代码篇思考题集锦(一) 答疑篇:代码篇思考题集锦(一)题的答案。因此呢,我特地将这个课程涉及的思考题进行了梳理,把其中的 67 个问题的答案或者说解题思路,详细地写了出来,并整理成了一个"答疑篇"模块。 我把这些问题拆分为了 6 篇分别更新,你可以根据自己的
  • Java开发常见错误100例-30如何正确保存和传输敏感数据? 30 如何正确保存和传输敏感数据? 今天,我们从安全角度来聊聊用户名、密码、身份证等敏的散列、对称加密和非对称加密算法,以及 HTTPS 等相关知识。 应该怎样保存用户密码? - 最敏感的数据恐怕就是用户的密
  • Java开发常见错误100例-15序列化:一来一回你还是原来的你吗? 15 序列化:一来一回你还是原来的你吗? 今天,我来和你聊聊序列化相关的坑和最佳实践。 序列化是把对象转换为字节流的过程,以方便传输或存储。反序列化,则是反过来把字节流转换为对象化,则是反过来把字节流转换为对象的过程。在介绍文件
  • Java开发常见错误100例-23缓存设计:缓存可以锦上添花也可以落井下石 23 缓存设计:缓存可以锦上添花也可以落井下石 今天,我从设计的角度,与你聊聊缓存。 通常我们会使用更快的介质(比如内存)作(比如磁盘)读取数据慢的问题,缓存是用空间换时间,来解决性能问题的一种架构设计模式。更重要的是,磁盘上存储
  • Java开发常见错误100例-17别以为“自动挡”就不可能出现OOM 17 别以为"自动挡"就不可能出现OOM 今天,我要和你分享的主题是,别以为"自动挡"就不可能出现 OOM。 这里的"自动挡",是我自动垃圾收集器的戏称。的确,经过这么多年的发展,Java 的垃圾收集器已经非常成熟了。有了自动垃圾
  • Java开发常见错误100例-结束语写代码时,如何才能尽量避免踩坑? 结束语 写代码时,如何才能尽量避免踩坑? -余时间都用了在这个课程的创作,以及回答你的问题上,很累很辛苦,但是看到你的认真学习和对课程内容的好评,看到你不仅收获了知识还燃起了钻研源码的热情,我也非常高兴,深觉一切的辛苦付出都是甜蜜的。
  • 近期文章

    更多
    文章目录

      推荐作者

      更多