SpringBoot(五)——SpringSecurity和Shiro

个人博客:http://blog.kunpw.cn

1.初始化导入资源

  • 新建项目并配置SpringWeb、Thymeleaf和SpringSecurity依赖(注:只要配置了SpringSecurity依赖后续资源在访问时就会自动被拦截并跳转到登录页,即使并没有配置登录账号);

  • 导入素材,资源链接:https://pan.baidu.com/s/1CsbZrhFKggnucYPHFVaHSw 提取码:v7g7

  • 新建Controller目录,下建RouterController.java路由控制器,实现简单路由即可:

    @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")Integer id){
            return "views/level1/"+id;
        }
        @RequestMapping("/level2/{id}")
        public String level2(@PathVariable("id")Integer id){
            return "views/level2/"+id;
        }
        @RequestMapping("/level3/{id}")
        public String level3(@PathVariable("id")Integer id){
            return "views/level3/"+id;
        }
    }
    

2.SpringSecurity权限及认证

SpringSecurity官方文档这里有简单的Security配置,按照此模式配置自己的config文件:

官方文档链接:https://docs.spring.io/spring-security/site/docs/5.3.8.RELEASE/reference/html5/#servlet-authentication-jdbc

image-20210311171442784

2.1 权限及认证

  • 新建SecurityConfig.java代码,实现权限及认证功能:

  • 这里主要是查看源码WebSecurityConfigurerAdapter.java文件中相关重写方法,照本临摹实现一些小功能

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /* 查看源码中该方法进行类比重写
            authorizeRequests()是授权请求
            antMatchers()为不同路径设置访问权限
        */
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/*").hasRole("vip1")
                .antMatchers("/level2/*").hasRole("vip2")
                .antMatchers("/level3/*").hasRole("vip3");
        // 没有授权则会默认进入登录页面,查看源码可知自动跳转/login
        http.formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /*  inMemoryAuthentication()是从内存中获取认证信息,还有jdbcAuthentication()从数据库中获取认证信息这里暂未连接数据库
            从源码示例中拿出.withUser("user").password("password").roles("USER").and()模板使用
            但是这种未加密密码传输安全受到威胁,需要使用passwordEncoder()加密,至于加密方式BCryptPasswordEncoder()则看需求
        * */
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3").and()
                .withUser("zhaoan").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2").and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }
}

有关于JDBC数据库连接认证看这里:可能本人后续也会具体讲解

image-20210311193523685

image-20210311193130575

2.2 定制登录页及注销

  • 定制登录页以及注销,修改上面的代码即可:
// 没有授权则会默认进入登录页面,查看源码可知自动跳转/login
// 定制跳转登录页,并绑定表单中用户名和密码
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password");
// 开启注销及跳转功能
http.logout().logoutSuccessUrl("/");
// 可能需要开启防跨站get访问,注销时使用
http.csrf().disable();

并且修改自定义login.html页面表单提交:

<!-- Post请求是Security默认设置的认证,以及之后跳转,而不是RouterController实现 -->
<form th:action="@{/toLogin}" method="post">

2.3 Thymeleaf-SpringSecurity搭配使用注销及用户显示

添加依赖:

<!-- thymeleaf-SpringSecurity依赖,注意此版本可能过旧,只能使用SpringBoot2.0.9最高,但本人2.4.3可以正常使用 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

index.html:初次之外记得上一点中设置的logout相关

且此处使用的ui为:semantic-ui,官网国内引入版:https://zijieke.com/semantic-ui/elements/icon.php

<!--引入sec命名-->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
                xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<!--登录注销-->
<div class="right menu">
    <!--未登录-->
    <div sec:authorize="!isAuthenticated()">
        <a class="item" th:href="@{/toLogin}">
            <i class="sign-in icon"></i> 登录
        </a>
    </div>

    <!--已登录,显示用户名和注销-->
    <div sec:authorize="isAuthenticated()">
        <a class="item">
            用户名:<span sec:authentication="name"></span></a>
    </div>
    <div sec:authorize="isAuthenticated()">
        <a class="item" th:href="@{/logout}">
            <i class="sign-out icon"></i> 注销
        </a>
    </div>
</div>

2.4 权限分级显示

在每一栏上增加此权限分级认证语句即可

<div class="column" sec:authorize="hasRole('vip3')">

2.5 记住我功能

login.html:在提交按钮上面增加此复选框

<div class="field">
    <input type="checkbox" name="remember">记住我
</div>

SecurityConfig.java

// 开启记住我且绑定表单参数
http.rememberMe().rememberMeParameter("remember");

3.Shiro整合使用

3.1 Shiro架构及初始化

  • Subject:
    • 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject,Subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager;;Subject其实是一个门面,SecurityManageer才是实际的执行者;
  • ShiroSecurityManager:
    • 安全管理器,即所有与安全有关的操作都会与SercurityManager交互,并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色;
  • Realm:
    • Shiro从Realm获取安全数据(如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看成DataSource;

image-20210312105829143

  1. 新建项目,添加Web和Thymeleaf依赖,以及添加Shiro-Spring依赖:

    <!-- 引入shiro-spring依赖 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.7.1</version>
    </dependency>
    
  2. 新建前端资源:

    • templates目录下新建index.html,user/vip1.html、user/vip2.html三个简单文件,只需添加文字表示各页面即可;
  3. 新建controller包,下建MyController.java文件,简单配置路由:

    @Controller
    public class MyController {
        @RequestMapping({"/","/index"})
        public String index(Model model){
            model.addAttribute("msg","hello,shiro");
            return "index";
        }
        @RequestMapping("/user/vip1")
        public String vip1(Model model){
            model.addAttribute("msg","hello,vip1");
            return "user/vip1";
        }
        @RequestMapping("/user/vip2")
        public String vip2(Model model){
            model.addAttribute("msg","hello,vip2");
            return "user/vip2";
        }
    }
    

    并对前端html文件设置Thymeleaf模板接收参数显示在页面上,并在首页增加跳转链接到其余两个页面,这个内容和SpringSecurity初始化资源差不多。

  4. 新建config包,下建UserRealm.java文件和ShiroConfig.java文件:

    UserRealm.java

    // 自定义 UserRealm extends AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            return null;
        }
    }
    

    ShiroConfig.java

    @Configuration
    public class ShiroConfig {
        // 1.创建realm对象,需要自定义类
        @Bean
        public UserRealm userRealm(){
            return new UserRealm();
        }
        // 2.DefaultWebSecurityManager @Qualifier("userRealm")表示绑定方法userRealm,有两种方式如下
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 关联userRealm
            securityManager.setRealm(userRealm);
            return securityManager;
        }
        // 3.ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            // 设置安全管理器
            bean.setSecurityManager(securityManager);
            return bean;
        }
    }
    

    此时就完成了简单的Shiro配置以及前端页面的链接跳转,向SpringSecurity一样,接下来其实就是登录、权限、注销等功能实现。

3.2 拦截器及跳转登录

  1. 增加登录页,新建login.html,实现简单登录表单:

    <h1>登录</h1>
    <hr>
    <form action="">
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="text" name="password"></p>
        <p><input type="submit"></p>
    </form>
    
  2. 设置路由,在MyController.java文件中增加,实现路由:

    @GetMapping("/login")
    public String login(){
        return "login";
    }
    
  3. ShiroConfig.java文件中shiroFilterFactoryBean()方法中增加过滤设置以及无权限跳转登录设置

    // 添加shiro内置过滤器
    /*  anon    无需认证就可以访问
        authc   必须拥有认证才可以访问
        user    必须拥有记住我功能才能访问
        perms   拥有对某个资源的权限才能访问
        role    拥有某个角色权限才能访问
    */
    Map<String,String> filterMap = new LinkedHashMap<>();
    filterMap.put("/user/*","authc");
    
    bean.setFilterChainDefinitionMap(filterMap);
    // 设置登录请求,即拦截之后跳转页面
    bean.setLoginUrl("/login");
    

3.3 整合Mybatis、druid认证登录

  1. 添加依赖:

    <!-- lombok可自动注入有参无参构造等 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- 引入mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- 引入mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.1</version>
    </dependency>
    <!-- 引入druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.4</version>
    </dependency>
    <!-- 引入log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    

    如四中新建log4j.properties文件并初始化:

    log4j.rootLogger=DEBUG, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    
  2. application.yml文件(没有该文件在application.properties同级创建该文件)中增加数据库连接druid数据源等配置:

    spring:
      datasource:
        username: root
        password: password
        # serverTimezone=UTC是时区,
        url: jdbc:mysql://localhost:3306/springboot_mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
        # Spring默认不注入这些属性配置,需要自己绑定,一般根据公司需要个性绑定,也是druid专有属性
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        # 配置filter,stat:监控统计;log4j:日志记录;wall:防御sql注入
        filters: stat,wall,log4j
        maxPoolPreparedStatmentPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    # 整合mybatis,分别为实体类位置和mapper实现层位置,这里暂未使用,等会用得上
    mybatis:
      type-aliases-package: com.kun.pojo
      mapper-locations: classpath:mapper/*.xml
    
  3. MyController.java接收post请求:

    login.html

    <h1>登录</h1>
    <hr>
    <p th:text="${msg}" style="color: red"></p>
    <form action="/login" method="post">
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="password" name="password"></p>
        <p><input type="submit"></p>
    </form>
    

    MyController.java增加post请求:

    // 接收表单数据并封装成token存入subject中,传给UserRealm中认证
    @PostMapping("/login")
    public String loginAuthentication(
        @PathVariable("username") String username,
        @PathVariable("password") String password,
        Model model){
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        // 封装当前用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            // 将该用户token传给认证doGetAuthenticationInfo
            subject.login(token);
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
        return "index";
    }
    
  4. 新建pojo、mapper和resources下mapper三个包:

    pojo.User.java

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    

    mapper.UserMapper.java

    @Mapper
    @Repository
    public interface UserMapper {
        User getUserByName(String name);
    }
    

    mapper.UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 注意此处需要设置连接位置 这里使用连接的数据库中的user表,表中属性为id,user,pwd-->
    <mapper namespace="com.kun.mapper.UserMapper">
        <select id="getUserByName" parameterType="String" resultType="User">
            select * from user where name = #{name}
        </select>
    </mapper>
    
  5. 再建service包(服务层,具体各层以及常用注解请参看本人博客SpringBoot原理),下建UserService.javaUserServiceImpl.java两个文件:

    UserService.java

    public interface UserService {
        User getUserByName(String name);
    }
    
    

    UserServiceImpl.java

    @Service
    public class UserServiceImpl implements UserService{
        @Autowired
        UserMapper userMapper;
    
        @Override
        public User getUserByName(String name) {
            return userMapper.getUserByName(name);
        }
    }
    
  6. UserRealm.java实现认证即可正常使用数据库用户登录:

    // 自定义 UserRealm extends AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
        @Autowired
        UserService userService;
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;
        }
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("执行了=>认证doGetAuthenticationInfo");
            // 将传入的认证token转换成Controller中封装的原token
            UsernamePasswordToken userToken = (UsernamePasswordToken) token;
    
            // 连接真实数据库获取数据
            User user = userService.getUserByName(userToken.getUsername());
            // 没有此人只要返回null即可,会自动将其识别为UnknownAccountException回到
            if(user==null){
                return null;
            }
            // 密码认证,shiro自己做,而不需要自己接触密码,SimpleAuthenticationInfo使用简单认证,没有加密,可以尝试加密
            // 查看SimpleAuthenticationInfo源码实现加密传输密码,md5,md5盐值加密
            return new SimpleAuthenticationInfo("",user.getPwd(),"");
        }
    }
    

3.4 授权

  1. 修改user表,增加perms权限一栏,并为部分用户授权:

    image-20210312151131121

  2. 增加pojo下User实体类属性perms:

    private String perms;
    
  3. MyController.java文件中增加无权限访问路由:

    // @ResponseBody 是返回字符串到空白页面
    @RequestMapping("/noauth")
    @ResponseBody
    public String noauth(){
        return "没有权限无法访问";
    }
    
  4. ShiroConfig.java文件中设置权限拦截以及无权限时跳转页面:

    /*filterMap.put("/user/*","authc");*/
    // 设置权限拦截
    filterMap.put("/user/vip1","perms[user:vip1]");
    filterMap.put("/user/vip2","perms[user:vip2]");
    // 设置无权限跳转页面
    bean.setUnauthorizedUrl("/noauth");
    
  5. UserRealm.java文件认证doGetAuthenticationInfo()方法中修改返回值方法传参:

    // 第一个参数为principal,将该参数存到subject中,由授权获取
    return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    

    对授权doGetAuthorizationInfo()方法作如下操作:

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行了=>授权doGetAuthorizationInfo");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Subject subject = SecurityUtils.getSubject();
    
        // 获取当前用户Subject的Principals,即为认证最后返回存储的user
        User currentUser = (User)subject.getPrincipal();
        // 为当前info增加当前Subject的权限,set可以设置一个集合的权限
        info.addStringPermission(currentUser.getPerms());
    
        return info;
    }
    

    即可成功授权访问。

3.5 shiro-thymeleaf

  1. 在maven仓库查询thymeleaf-shiro,添加最新版本进入依赖:

    pom.xml

    <!-- thymeleaf-shiro -->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
    
  2. ShiroConfig.java文件中增加方法整合二者,并加入bean,没有此方法html中引用shiro不会成功:

    // 整合shiro-thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
    
  3. 修改首页index.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
          xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        <h1>首页</h1>
        <div th:text="${msg}"></div>
        <!-- 从session中判断值 -->
        <div th:if="${session.userLogin==null}">
            <a th:href="@{/login}">登录</a>
        </div>
        <hr>
        <!-- 使用thymeleaf-shiro获取用户权限 -->
        <div shiro:hasPermission="user:vip1">
            <a th:href="@{/user/vip1}">vip1</a>
        </div>
        <div shiro:hasPermission="user:vip2">
            <a th:href="@{/user/vip2}">vip2</a>
        </div>
    </body>
    </html>
    
  4. MyController.java文件中loginAuthentication()方法中增加Session:

    // 接收表单数据并封装成token存入subject中,传给UserRealm中认证
    @PostMapping("/login")
    public String loginAuthentication(
        String username, String password,
        Model model){
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        // 封装当前用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            // 将该用户token传给认证doGetAuthenticationInfo
            subject.login(token);
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
        // 对当前subject用户设置session
        Session session = subject.getSession();
        session.setAttribute("userLogin",subject);
        return "index";
    }
    

取当前用户
Subject subject = SecurityUtils.getSubject();
// 封装当前用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
// 将该用户token传给认证doGetAuthenticationInfo
subject.login(token);
}catch (UnknownAccountException e){
model.addAttribute(“msg”,“用户名错误”);
return “login”;
}catch (IncorrectCredentialsException e){
model.addAttribute(“msg”,“密码错误”);
return “login”;
}
// 对当前subject用户设置session
Session session = subject.getSession();
session.setAttribute(“userLogin”,subject);
return “index”;
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值