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配置好(这里不说了,学安全的都会了)

基本配置讲解

项目结构是这样的:

Spring Security 简单入门(一)_java

整合很简单,在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的操作。是不是很简单。

登录页面: 这个页面不会被拦截

Spring Security 简单入门(一)_ide_02

点击记住我之后,会生成一个token放在Cookies里面

Spring Security 简单入门(一)_xml_03

整个验证过程就是这样。

错误页面的定制

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


  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 Security 简单入门(一)_xml_04

对应的下面那个角色也是如此,但是这个有个问题就是,如果知道这个系统的资源路径,还是可以直接通过该路劲拿到资源,所以还是相当的不安全,所以可以进行注解方式的权限验证

首先在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上了,文章上面有地址,谢谢大家的阅读!!欢迎评论留言讨论!!