spring security 是 spring 提供用于实现简单的登陆和用户权限控制的框架,其实就是通过过滤器拦截请求,对用户进行认证和授权,放行当前权限可访问的页面。
第一步:添加spring security jar 包到项目中, 这里使用的是 spring security5
<!-- spring-security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
第二步: web.xml 中配置过滤器拦截请求
<!-- Filter 3: Spring Security Filter -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第三步: 配置 spring-security.xml
<!-- 匹配的 URL 不进行身份验证 -->
<http security="none" pattern="/page/login"/>
<!-- 禁止创建 session,使用 token 的方式进行认证, auto-config 使用默认的设置 -->
<http auto-config="true" create-session="stateless" use-expressions="false" access-decision-manager-ref="accessDecisionManager">
<!-- 根据权限设置可访问页面 -->
<intercept-url pattern="/page/courseware" access="ROLE_USER"/>
<!-- 配置 form-login 表单验证 -->
<form-login login-page="/page/login"
login-processing-url="/login"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-url="/page/login?error"
username-parameter="username"
password-parameter="password"/>
<!-- 注销 logout-url 注销的请求 logout-success-url 注销跳转的目标地址 delete-cookies
注销后删除的 cookies 信息的 key 值-->
<logout logout-url="/logout" logout-success-url="/page/login?logout" delete-cookies="user-info"/>
<!-- 当登陆用户权限不够时跳转页面 -->
<access-denied-handler error-page="/page/deny"/>
<!-- 防止其他网页操作该系统 -->
<csrf disabled="true"/>
<!-- 设置浏览器记住我 -->
<remember-me key="uniqueAndSecret" token-validity-seconds="2592000"/>
<!-- 自定义过滤器,用户账号校验时可以写过滤器根据需求特殊处理。 -->
<custom-filter ref="authenticationFilters" before="FORM_LOGIN_FILTER"/>
</http>
form-login 中的每个属性详解参照: https://blog.csdn.net/weixin_40648117/article/details/78915205
spring-security.xml中最关键的就是用户登录时的账号检验功能,该功能大概有三种方式:
第一种也是最简单的,适用于系统只有几个用户的管理平台使用。
<!-- 系统只有两个用户直接使用 user-service 写死 -->
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<!-- 这里创建两个用户,可以通过用户名密码登录 -->
<user name="adminSystem" password="123456" authorities="ROLE_USER"/>
<user name="admin" password="admin" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
<!-- 设置密码不加密 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
使用这两个账号登陆表单就可以登陆系统,spring security 5 之前都要去设置密码的加密规则,如果不想处理密码可以使用上面的不加密配置。如果想要做加密处理可参照下面的方式
<authentication-manager>
<authentication-provider>
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="passwordEncoder" class="com.lwy.security.controller.PasswordEncoderImpl" />
PasswordEncoderImpl 类中写你的密码加密的策略,spring security 会自动将用户输入的密码通过这个加密策略加密后,根据数据库中密码校验。
第二种 使用数据库连接直接校验
<authentication-manager>
<authentication-provider user-service-ref='userDetailsService' />
</authentication-manager>
<beans:bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
<beans:property name="usersByUsernameQuery" value="SELECT username, password FROM user WHERE username = ?" />
</beans:bean>
这种方式简单易懂没啥好说的。
第三种 创建 java 类实现 org.springframework.security.core.userdetails.UserDetailsService ,重写 loadUserByUsername 方法,通过用户名获取用户信息。
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findUserByUserName(username);
if (user == null) {
throw new UsernameNotFoundException(username + " not found!");
}
user = User.userForSpringSecurity(user); // 构建 Spring Security 需要的用户
return user;
}
没有获取到直接登陆失败,系统中没有这个用户,如果获取到了会有一条用户信息,这条用户信息就是用于和用户输入的用户信息进行比对用的。
第五步 登陆成功处理用户信息,在 form-login 中有个属性 authentication-success-handler-ref="authenticationSuccessHandler",用于指向登陆成功要执行的方法,一般可以在这里对登陆成功的用户信息进行处理,比如将用户信息存于 cookie 用于用户浏览时的实时的登陆状态检验,也可以将用户信息处理为 jwt 的 token形式来存储到 cookie 中,当用户访问别的请求时就可以从 cookie 中取出信息校验登陆状态。
<beans:bean id="authenticationSuccessHandler" class="com.lwy.security.AuthenticationSuccessHandler"/>
对应的Java 类
package com.lwy.security;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AuthenticationSuccessHandler implements org.springframework.security.web.authentication.AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
// 用户名密码查询用户 (访问这个函数,说明是通过表单成功登录过来的,使用用户名密码一定能够查询到用户)
String username = request.getParameter(SecurityConstant.LOGIN_USERNAME);
String password = request.getParameter(SecurityConstant.LOGIN_PASSWORD);
// 将用户名称保存到 cookie
WebUtils.writeCookie(response, SecurityConstant.USER_INFO_KEY, username, authTokenDuration);
// 根据用户名查询用户信息
User user = userService.findUserByUserName(username);
// 生成 Spring Security 可使用的用户对象,保存到 SecurityContext 供 Spring Security 使用
user = User.userForSpringSecurity(user);
Authentication auth = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
// 通过用户名区分角色跳转不同的页面
if (username.equals("admin")) {
// 普通管理员
response.sendRedirect("/page/courseware");
} else if (username.equals("adminSystem")) {
// 系统管理员
response.sendRedirect("/page/courseware");
}
}
}
下面是每次用户访问请求时 cookie 校验的代码, 需要继承 AbstractAuthenticationProcessingFilter
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 已经登录过直接放行, 为空则进入下一个过滤器使用表单登录验证
if (WebUtils.getCookie(request, SecurityConstant.USER_INFO_KEY) != null) {
Authentication auth = attemptAuthentication(request, response);
if (auth == null) {
if (WebUtils.useAjax(request)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "登录已过期");
} else {
WebUtils.deleteCookie(response, SecurityConstant.AUTH_TOKEN_KEY);
response.sendRedirect(Urls.PAGE_LOGIN);
}
return;
} else {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
// 继续调用下一个 filter: UsernamePasswordAuthenticationToken 进行表单验证
chain.doFilter(request, response);
}
还有 spring security5 提供一些默认的密码加密方式,存储密码的时候使用下面几种方式,spring security5 可以自动根据密码规则检验密码。
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.crypto.factory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
import org.springframework.security.crypto.password.Md4PasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import java.util.HashMap;
import java.util.Map;
/**
* Used for creating {@link PasswordEncoder} instances
* @author Rob Winch
* @since 5.0
*/
public class PasswordEncoderFactories {
/**
* Creates a {@link DelegatingPasswordEncoder} with default mappings. Additional
* mappings may be added and the encoding will be updated to conform with best
* practices. However, due to the nature of {@link DelegatingPasswordEncoder} the
* updates should not impact users. The mappings current are:
*
* <ul>
* <li>bcrypt - {@link BCryptPasswordEncoder} (Also used for encoding)</li>
* <li>ldap - {@link LdapShaPasswordEncoder}</li>
* <li>MD4 - {@link Md4PasswordEncoder}</li>
* <li>MD5 - {@code new MessageDigestPasswordEncoder("MD5")}</li>
* <li>noop - {@link NoOpPasswordEncoder}</li>
* <li>pbkdf2 - {@link Pbkdf2PasswordEncoder}</li>
* <li>scrypt - {@link SCryptPasswordEncoder}</li>
* <li>SHA-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}</li>
* <li>SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}</li>
* <li>sha256 - {@link StandardPasswordEncoder}</li>
* </ul>
*
* @return the {@link PasswordEncoder} to use
*/
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
private PasswordEncoderFactories() {}
}
上面就是我对 spring security 的一些使用总结,如果有说错和不准确的地方欢迎纠正谢谢。