Spring Security 简单入门(一)

Spring Security 简单入门(一)

Spring Security简介

安全框架,很强大,进入正题。
刚看完视频,总结一下:

  1. 对于学一门新的技术,建议新手视频起步,就当看电视剧。
  2. 多看别人写的博客,分析分析,得出适合自己的一套学习思路。
  3. 学完之后可以自己写篇博客总结,不要觉得很浪费时间,我自己也有这种想法,其实就是懒,而我这几天也是一直想学新的东西,所以不想去写博客,但是这样很容易让自己所学的东西很快就会忘掉,虽说了解就行,不懂百度,但是如果熟练运用岂不是更能“膨胀”?当然,这些东西都是IT前辈们玩剩下的东西,不值得一提,正题正题…就是多总结,也可以跟道友们一起探讨如何成仙…交流一波内功心法和天地感悟,才能渡劫成功!
  4. 希望这次疫情可以快点好起来,武汉加油。

整合Security的三种方式

  1. ssm + jsp
  2. springboot + jsp
  3. 微服务中使用

整合的环境

  1. idea 2019 3.1 (学生教育网免费,还有就是破解(有钱最好还是买,哈哈哈)…)
  2. Chrome 有条件的最好在写个firefox
  3. 差不多了 tomcat mysql 都会有

ssm + jsp整合

代码已经上传到我的githup上:githup代码

  1. 将代码拷到idea上,里面有db文件夹,有个.sql文件
  2. 可以先把tomcat配置好(这里不说了,学安全的都会了)

基本配置讲解

项目结构是这样的:
在这里插入图片描述
整合很简单,在pom文件里添加两个依赖:

		<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

然后编写spring-security.xml文件即可,这里需要注意的是,需要在spring的配置文件(applicationContext.xml)中,引用一下这个配置文件:

<import resource="classpath:spring-security.xml"/>

接下来就说下核心配置文件spring-security.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">


    <!--直接释放无需经过SpringSecurity过滤器的静态资源-->
    <security:http pattern="/css/**" security="none"/>
    <security:http pattern="/img/**" security="none"/>
    <security:http pattern="/plugins/**" security="none"/>
    <security:http pattern="/failer.jsp" security="none"/>
    <security:http pattern="/favicon.ico" security="none"/>
    <!--设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)-->
    <security:http auto-config="true" use-expressions="true">
        <!--指定login.jsp页面可以被匿名访问-->
        <security:intercept-url pattern="/login.jsp" access="permitAll()"/>
        <!--使用spring的el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色-->
        <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
        <!--指定自定义的认证页面-->
        <security:form-login login-page="/login.jsp"
                             login-processing-url="/login"
                             default-target-url="/index.jsp"
                             authentication-failure-url="/failer.jsp"/>
        <!--指定退出登录后跳转的页面-->
        <security:logout logout-url="/logout"
                         logout-success-url="/login.jsp"/>

        <security:csrf disabled="true"/>

        <!--<security:access-denied-handler error-page="/403.jsp"/>-->

        <security:remember-me
                data-source-ref="dataSource"
                token-validity-seconds="60"
        />

    </security:http>
    <!--设置Spring Security认证用户信息的来源-->

    <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

    <security:authentication-manager>
        <security:authentication-provider user-service-ref="userServiceImpl">
            <security:password-encoder ref="passwordEncoder"/>
        </security:authentication-provider>
    </security:authentication-manager>


</beans>

上面文件大部分都有注解,看注解就可以看懂,没注解的讲讲是什么意思:

  1. login-page="/login.jsp": 这个是设置登录界面,如果不设置,spring security会有个默认的登录界面,设置了回去根路径下找这个页面,
  2. login-processing-url="/login": 这个是处理路径,不需要我们自己定义在controller层,spring security会帮我处理,之后再讲是怎么处理的
  3. default-target-url="/index.jsp": 这个是处理成功后的默认跳转页面
  4. authentication-failure-url="/failer.jsp: 这个是处理失败后的页面,后面会讲自定义错误页面
  5. <security:csrf disabled=“true”/>: 这个是关闭post的跨域拦截,当然如果带上token的话就不需要关闭,而且关闭很不安全
<security:authentication-manager>
        <security:authentication-provider user-service-ref="userServiceImpl">
            <security:password-encoder ref="passwordEncoder"/>
        </security:authentication-provider>
    </security:authentication-manager>

这里是配置基于什么去验证登录,这里是基于数据库数据进行验证的,还有很多种,可自行百度,这里讲的就是常用的基于数据库的方式。
还有一个小功能:

<security:remember-me
                data-source-ref="dataSource"
                token-validity-seconds="60"
        />

这个是记住我的功能,加上这个在第二次访问的时候,可以实现免登录。
第一行的配置是,指定数据源
第二行的配置是,指定token的过期时间

代码讲解

想让系统使用安全框架的特性,只需要让对应登录用户的UserService继承UserDetailsService:
UserDetailsService.java代码:

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

从这个源码中可以知道,我们只需要实现loadUserByUsername(String var1)这个方法即可,在实现类中写业务逻辑:

 @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            // 查找数据库中的数据
            SysUser sysUser = userDao.findByName(username);
            // 添加该用户对应权限集合
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            List<SysRole> roles = sysUser.getRoles();
            for (SysRole role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
            }
            // 传入用户名和密码交由 框架进行匹配
            UserDetails userDetails = new User(sysUser.getUsername(),
                    sysUser.getPassword(),
                    sysUser.getStatus() == 1,
                    true,
                    true,
                    true,
                    authorities);
            return userDetails;
        } catch (Exception e) {
            e.getStackTrace();
            // 返回空就是认证失败 内部会判断
            return null;
        }
    }

根据上面那个方法不难看出,整个验证过程,我们只需要返回一个UserDetails对象给Spring Security即可,就会帮我们做处理,讲解一下里面的代码:

List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            List<SysRole> roles = sysUser.getRoles();
            for (SysRole role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
            }

这部分是对该用户进行授权,我们可以看看SimpleGrantedAuthority.java

package org.springframework.security.core.authority;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;

public final class SimpleGrantedAuthority implements GrantedAuthority {
    private static final long serialVersionUID = 510L;
    private final String role;

    public SimpleGrantedAuthority(String role) {
        Assert.hasText(role, "A granted authority textual representation is required");
        this.role = role;
    }

    public String getAuthority() {
        return this.role;
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else {
            return obj instanceof SimpleGrantedAuthority ? this.role.equals(((SimpleGrantedAuthority)obj).role) : false;
        }
    }

    public int hashCode() {
        return this.role.hashCode();
    }

    public String toString() {
        return this.role;
    }
}

里面主要的是一个构造器,需要传入角色,还有就是一个返回角色信息的方法,可以看看这个类实现的接口,那个接口在springboot中就会用到,现在不需要,只需要用这个类即可。
不难看出,每个用户可能对应很多角色信息,所以这里使用List接收。这部分代码就是获取所有该用户所有的权限,添加多个SimpleGrantedAuthority实例。

在接下来看的就是Spring Security的核心接口UserDetails.java:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.security.core.userdetails;

import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

里面的第一个方法便是获得所有角色,然后还有获取用户名和密码,看这个接口看不出啥,看看他的实现类的部分源码:

	public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        if (username != null && !"".equals(username) && password != null) {
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
            this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
        } else {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }

从上面代码中不难看出,该实现类有两种不同的构造器,用最简单的那种,我们只需要传入三个参数,是不是就是我们前面那个实现类获取到的数据,我继续看下面这部分代码:

UserDetails userDetails = new User(sysUser.getUsername(),
                    sysUser.getPassword(),
                    sysUser.getStatus() == 1,
                    true,
                    true,
                    true,
                    authorities);
            return userDetails;

这里用的是第二个构造器,多了一个参数boolean enabled这个参数的作用是…对用户的禁用类似的一个功能,但不喜欢这样做的,可以自行用代码实现,跟以往普通的登录验证在这里也都可以实现,只是这里需要在loadUserByUsername这个方法里面写逻辑。然后接下来几个参数就是那几个需要返回boolean值的了,使用第一个构造器的时候就不需要这么多的参数了,只需要三个参数即可。
然后返回一个userDetails实例即可,整个代码业务过程就是这样。

在页面上进行验证

打开login.jsp页面,我这里只粘贴部分代码:

<%@taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
...
<form action="${pageContext.request.contextPath}/login" method="post">
			<security:csrfInput/>

最上面那行就是引入一个标签库,而form表单需要将请求路径改成/login,而不是页面跳转到login.jsp了,并且需要加上post请求。
而下面的那行代码就是为post请求加上跨域拦截需要的token,后续可以使用jwt生成系统需要的token,这里只需要加入这个标签就可以完成加入token的操作。是不是很简单。
登录页面: 这个页面不会被拦截
在这里插入图片描述
点击记住我之后,会生成一个token放在Cookies里面
在这里插入图片描述
整个验证过程就是这样。

错误页面的定制

自定义错误页面很简单,可以有三种方式:

  1. 在springmvc.xml的配置文件中配置即可,这个不说
  2. 在spring-security.xml文件中配置即可:
<security:access-denied-handler error-page="/403.jsp"/>

这样所有的access-denied这种异常,就会跳转到指定的页面

  1. 全局捕获异常,自定义一个HandlerControllerAdvice类:
package com.itheima.exception;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;


/**
 * 功能描述:
 *
 * @author wcx
 * @version 1.0
 */
@ControllerAdvice
public class HandlerControllerAdvice {

    @ExceptionHandler(AccessDeniedException.class)
    public String handlerException() {
        return "forward:/403.jsp";
    }

    @ExceptionHandler(RuntimeException.class)
    public String runtimeHandlerException() {
        return "forward:/500.jsp";
    }
}

整个错误页面的定制也就是这样

权限验证

还有就是一个权限验证,可以通过分配页面的方式来控制权限,这样的方式很简单,如下代码:

				<ul class="treeview-menu">
                    <security:authorize access="hasAnyRole('ROLE_PRODUCT', 'ROLE_ADMIN')">
                        <li id="system-setting"><a
                                href="${pageContext.request.contextPath}/product/findAll">
                            <i class="fa fa-circle-o"></i> 产品管理
                        </a></li>
                    </security:authorize>
                    <security:authorize access="hasAnyRole('ROLE_ORDER', 'ROLE_ADMIN')">
                        <li id="system-setting"><a
                                href="${pageContext.request.contextPath}/order/findAll">
                            <i class="fa fa-circle-o"></i> 订单管理
                        </a></li>
                    </security:authorize>
                </ul>

当然上面也是需要引入标签库的,这样做可以让具有ROLE_PRODUCT这个角色的时只能看到其对应的资源:
在这里插入图片描述
对应的下面那个角色也是如此,但是这个有个问题就是,如果知道这个系统的资源路径,还是可以直接通过该路劲拿到资源,所以还是相当的不安全,所以可以进行注解方式的权限验证

首先在spring-mvc.xml配置文件中加入:

<!--
    开启权限控制注解支持
    jsr250-annotations="enabled"表示支持jsr250-api的注解,需要jsr250-api的jar包
    pre-post-annotations="enabled"表示支持spring表达式注解
    secured-annotations="enabled"这才是SpringSecurity提供的注解
-->
    <security:global-method-security jsr250-annotations="enabled"
                                     pre-post-annotations="enabled"
                                     secured-annotations="enabled"/>

然后在代码上加上注解,就会对其他角色进行拦击,代码如下:

package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.security.RolesAllowed;

@Controller
@RequestMapping("/product")
@RolesAllowed({"ROLE_PRODUCT", "ROLE_ADMIN"})
public class ProductController {

    // @Secured() 这个是security自带的注解,内部的
    // @PreAuthorize() 这个是spring提供的注解
    @RequestMapping("/findAll")
    public String findAll(){
        return "product-list";
    }
}

可以自行尝试一下,很简单。

总结

则就是ssm + jsp方式对spring security进行整合,都很简单的,代码我已经上传到githup上了,文章上面有地址,谢谢大家的阅读!!欢迎评论留言讨论!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值