Spring Security 简单入门(一)
Spring Security简介
安全框架,很强大,进入正题。
刚看完视频,总结一下:
- 对于学一门新的技术,建议新手视频起步,就当看电视剧。
- 多看别人写的博客,分析分析,得出适合自己的一套学习思路。
- 学完之后可以自己写篇博客总结,不要觉得很浪费时间,我自己也有这种想法,其实就是懒,而我这几天也是一直想学新的东西,所以不想去写博客,但是这样很容易让自己所学的东西很快就会忘掉,虽说了解就行,不懂百度,但是如果熟练运用岂不是更能“膨胀”?当然,这些东西都是IT前辈们玩剩下的东西,不值得一提,正题正题…就是多总结,也可以跟道友们一起探讨如何成仙…交流一波内功心法和天地感悟,才能渡劫成功!
- 希望这次疫情可以快点好起来,武汉加油。
整合Security的三种方式
- ssm + jsp
- springboot + jsp
- 微服务中使用
整合的环境
- idea 2019 3.1 (学生教育网免费,还有就是破解(有钱最好还是买,哈哈哈)…)
- Chrome 有条件的最好在写个firefox
- 差不多了 tomcat mysql 都会有
- …
ssm + jsp整合
代码已经上传到我的githup上:githup代码
- 将代码拷到idea上,里面有db文件夹,有个.sql文件
- 可以先把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>
上面文件大部分都有注解,看注解就可以看懂,没注解的讲讲是什么意思:
- login-page="/login.jsp": 这个是设置登录界面,如果不设置,spring security会有个默认的登录界面,设置了回去根路径下找这个页面,
- login-processing-url="/login": 这个是处理路径,不需要我们自己定义在controller层,spring security会帮我处理,之后再讲是怎么处理的
- default-target-url="/index.jsp": 这个是处理成功后的默认跳转页面
- authentication-failure-url="/failer.jsp: 这个是处理失败后的页面,后面会讲自定义错误页面
- <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里面
整个验证过程就是这样。
错误页面的定制
自定义错误页面很简单,可以有三种方式:
- 在springmvc.xml的配置文件中配置即可,这个不说
- 在spring-security.xml文件中配置即可:
<security:access-denied-handler error-page="/403.jsp"/>
这样所有的access-denied这种异常,就会跳转到指定的页面
- 全局捕获异常,自定义一个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上了,文章上面有地址,谢谢大家的阅读!!欢迎评论留言讨论!!