首页 热点专区 小学知识 中学知识 出国留学 考研考公
您的当前位置:首页正文

Spring和Security整合详解

2024-12-15 来源:要发发知识网

Spring和Security整合详解

官方主页

概述

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

开始搭建

依赖Jar包

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.security.version}</version>
</dependency>

Security的安全配置

Spring整合Security需要配置Security的安全控制策略,这里先以form登录控制为例,后面文章会讲token系列。

FormSecurityConfig依赖于其他组件,后面会一一列出:

package com.cff.springwork.security.config;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

import com.cff.springwork.security.ajax.AjaxAuthFailHandler;
import com.cff.springwork.security.ajax.AjaxAuthSuccessHandler;
import com.cff.springwork.security.ajax.AjaxLogoutSuccessHandler;
import com.cff.springwork.security.ajax.UnauthorizedEntryPoint;
import com.cff.springwork.security.service.SimpleAuthenticationProvider;

@EnableWebSecurity
public class FormSecurityConfig {

    @Configuration                                                   
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
        @Autowired
        private SimpleAuthenticationProvider simpleAuthenticationProvider;
        
        @Autowired
        @Qualifier("formAuthenticationDetailsSource")
        private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource;
        
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(simpleAuthenticationProvider);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().exceptionHandling()
                    .authenticationEntryPoint(new UnauthorizedEntryPoint())
                    .and().headers()
                    .frameOptions().disable().and().authorizeRequests()
                    .antMatchers("/favicon.ico").permitAll()
                    .antMatchers("/login").permitAll()
                    .antMatchers("/login.html").permitAll()
                    .antMatchers("/css/**").permitAll()
                    .antMatchers("/pub/**").permitAll()
                    .antMatchers("/user/**").hasAnyRole("USER","ADMIN")
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .usernameParameter("userName").passwordParameter("userPwd")
                    .authenticationDetailsSource(authenticationDetailsSource)
                    .loginProcessingUrl("/login")
                    .successHandler(new AjaxAuthSuccessHandler())
                    .failureHandler(new AjaxAuthFailHandler())
                    .and()
                    .logout()
                    .logoutUrl("/logout").logoutSuccessHandler(new AjaxLogoutSuccessHandler());
        }
    }
}

Security的校验逻辑

SimpleAuthenticationProvider对form登录校验做了简单的控制:

package com.cff.springwork.security.service;

import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import 
import org.springframework.util.StringUtils;

import com.cff.springwork.security.detail.FormAddUserDetails;
import com.cff.springwork.security.detail.FormUserDetails;

@Component
public class SimpleAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private FormUserDetailsService formUserDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 验证码等校验
        FormAddUserDetails details = (FormAddUserDetails) authentication.getDetails();

        System.out.println(details.getToken() + "+++++++++++++++++" + details.getSessionToken());
        if (!details.getIsAjax() && !StringUtils.isEmpty(details.getSessionToken())) {
            if (!details.getToken().equalsIgnoreCase(details.getSessionToken())) {
                throw new BadCredentialsException("验证码错误。");
            }
        }

        // 用户名密码校验
        FormUserDetails formUserDetails = (FormUserDetails) formUserDetailsService
                .loadUserByUsername(authentication.getName());
        System.out.println(authentication.getName() + "+++++++++++++++++" + authentication.getCredentials());
        if (!formUserDetails.getUserName().equals(authentication.getName())
                || !formUserDetails.getPassword().equals(authentication.getCredentials())) {
            throw new BadCredentialsException("用户名或密码错误。");
        }
        Collection<? extends GrantedAuthority> authorities = formUserDetails.getAuthorities();
        return new UsernamePasswordAuthenticationToken(formUserDetails.getUsername(), formUserDetails.getPassword(),
                authorities);
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }

}

这里用到了两个实体FormAddUserDetails和FormUserDetails,分别是额外信息处理实体和校验实体,下面会介绍下两个实体。

SimpleAuthenticationProvider控制校验,需要数据库配合查询用户名密码,和前台传过来的用户名密码做对比,因此,需要一个UserDetailsService:

FormUserDetailsService:

package com.cff.springwork.security.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.cff.springwork.model.security.AppUser;
import com.cff.springwork.mybatis.service.AppUserService;
import com.cff.springwork.security.detail.FormUserDetails;

@Service("formUserDetailsService")
public class FormUserDetailsService implements UserDetailsService {
    @Autowired
    private AppUserService appUserService;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        AppUser user;
        try {
            user = appUserService.findByName(userName);
        } catch (Exception e) {
            throw new UsernameNotFoundException("user select fail");
        }
        if(user == null){
            throw new UsernameNotFoundException("no user found");
        } else {
            try {
                return new FormUserDetails(user);
            } catch (Exception e) {
                throw new UsernameNotFoundException("user role select fail");
            }
        }
    }

}

FormUserDetailsService仅能将用户名密码做校验,如果需要额外的校验信息,需要配置AuthenticationDetailsSource,将HttpServletRequest传递给给WebAuthenticationDetails:

FormAuthenticationDetailsSource:

package com.cff.springwork.security.service;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import 

import com.cff.springwork.security.detail.FormAddUserDetails;

@Component("formAuthenticationDetailsSource")
public class FormAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {

    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new FormAddUserDetails(context);
    }


}

下面是需要用到的安全实体。

安全控制实体

FormUserDetails负责存储用户名密码和权限相关信息:

package com.cff.springwork.security.detail;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;

import com.cff.springwork.model.security.AppUser;


public class FormUserDetails extends AppUser implements UserDetails{

    public FormUserDetails(AppUser appUser) {
        super(appUser);
    }

    /**
     * 
     */
    private static final long serialVersionUID = 6272869114201567325L;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList("USER");
    }

    @Override
    public String getUserType() {
        return super.getUserType();
    }

    @Override
    public String getUsername() {
        return super.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

AppUser是我自定义的一个实体,是从数据里查询的,这个仅贴出AppUser的代码,查询过程就不写了。

AppUser:

package com.cff.springwork.model.security;

import java.util.UUID;

public class AppUser {
    private String uuid;
    private String userName;
    private String password;
    private String userType;
    private String userNo;
    public AppUser() {
        
    }
    
    public AppUser(String userName, String password) {
        this.userName = userName;
        this.password = password;
        this.uuid = UUID.randomUUID().toString();
        this.userType = "1";
    }
    
    public AppUser(AppUser appUser) {
        this.userName = appUser.userName;
        this.password = appUser.password;
        this.uuid = appUser.uuid;
        this.userType = appUser.userType;
    }


    public String getUuid() {
        return uuid;
    }
    public void setUuid(String uuid) {
        this.uuid = uuid;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getUserType() {
        return userType;
    }
    public void setUserType(String userType) {
        this.userType = userType;
    }

    public String getUserNo() {
        return userNo;
    }

    public void setUserNo(String userNo) {
        this.userNo = userNo;
    }
    
}

FormAddUserDetails是存储一些额外的验证信息,如验证码等。

FormAddUserDetails:

package com.cff.springwork.security.detail;

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;


public class FormAddUserDetails extends WebAuthenticationDetails{
     /**
     * 
     */
    private static final long serialVersionUID = 6975601077710753878L;
    private final String token;
    private final String sessionToken;
    private final Boolean isAjax;
    public FormAddUserDetails(HttpServletRequest request) {
        super(request);
        isAjax = isAjaxRequest(request);
        token = request.getParameter("imgtoken");
        sessionToken = (String) request.getSession().getAttribute("imgtoken");
    }

    public String getToken() {
        return token;
    }

    public String getSessionToken() {
        return sessionToken;
    }

    public Boolean getIsAjax() {
        return isAjax;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(super.toString()).append("; Token: ").append(this.getToken());
        return sb.toString();
    }
    
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String ajaxFlag = request.getHeader("X-Requested-With");
        return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
    }

}

安全控制处理器

FormSecurityConfig中我们配置了很多处理器,有UnauthorizedEntryPoint、AjaxAuthFailHandler、AjaxAuthSuccessHandler、AjaxLogoutSuccessHandler。
顾名思义,这些是分别处理未授权、校验失败、校验成功、登出操作的,我们这里区分下ajax请求和跳转请求。

UnauthorizedEntryPoint:

package com.cff.springwork.security.ajax;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        if(isAjaxRequest(request)){
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage());
        }else{
            response.sendRedirect("/login.html");
        }

    }

    public static boolean isAjaxRequest(HttpServletRequest request) {
        String ajaxFlag = request.getHeader("X-Requested-With");
        return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
    }
}

AjaxAuthFailHandler:

package com.cff.springwork.security.ajax;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

public class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        if(isAjaxRequest(request)){
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed");
        }else{
            setDefaultFailureUrl("/login.html");
            super.onAuthenticationFailure(request, response, exception);
        }
    }
    
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String ajaxFlag = request.getHeader("X-Requested-With");
        return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
    }
}

AjaxAuthSuccessHandler:

package com.cff.springwork.security.ajax;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import 
import 
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.StringUtils;

public class AjaxAuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    protected final Log logger = LogFactory.getLog(this.getClass());
    
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.getSession().setAttribute("userName", authentication.getName());
        if(isAjaxRequest(request)){
            response.setStatus(HttpServletResponse.SC_OK);
        }else{
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
    
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String ajaxFlag = request.getHeader("X-Requested-With");
        return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
    }
}

AjaxLogoutSuccessHandler:

package com.cff.springwork.security.ajax;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;

public class AjaxLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler{
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        if(isAjaxRequest(request)){
            response.setStatus(HttpServletResponse.SC_OK);
        }else{
            super.onLogoutSuccess(request, response, authentication);
        }
    }
    
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String ajaxFlag = request.getHeader("X-Requested-With");
        return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
    }
}

快速构建项目

显示全文