spring security 使用详解

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 的一些使用总结,如果有说错和不准确的地方欢迎纠正谢谢。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值