(3)SpringBoot集成SpringSecurity前后分离解决方案-上

spring security 的核心功能主要包括:

认证 (你是谁)
授权 (你能干什么)
攻击防护 (防止伪造身份)
     其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。

实现基本思路:服务端通过 JSON字符串,告诉前端用户有没有登录、认证,前端根据这些提示跳转对应的登录页、认证页等。

1、HTTP返回结果

package com.grsoft.security.controller;

import java.io.Serializable;

/**
 * @Description: http返回结果
 * @author: :  Steven
 * @Date: 2020/2/13 16:29
 */
public class ResponseBody implements Serializable {

    private String status;
    private String msg;
    private Object result;
    private String jwtToken;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    public String getJwtToken() {
        return jwtToken;
    }

    public void setJwtToken(String jwtToken) {
        this.jwtToken = jwtToken;
    }

}

2、授权点的配置

2.1、未登录-AuthenticationEntryPoint

用户没有登录时返回给前端的数据

package com.grsoft.security.common;

import com.alibaba.fastjson.JSON;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description: 未登录
 * @author: :  Steven
 * @Date: 2020/2/13 16:54
 */
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();
        responseBody.setStatus("000");
        responseBody.setMsg("Need Authorities! 未登录");
        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

2.2、登录失败-AuthenticationFailureHandler

用户登录失败时返回给前端的数据

package com.grsoft.security.common;

import com.alibaba.fastjson.JSON;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description: 登录失败
 * @author: :  Steven
 * @Date: 2020/2/13 16:55
 */
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();
        responseBody.setStatus("400");
        responseBody.setMsg("Login Failure! 登录失败");
        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

2.3、登录成功-AuthenticationSuccessHandler

用户登录成功时返回给前端的数据

package com.grsoft.security.common;

import com.alibaba.fastjson.JSON;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * @Description: 登录成功
 * @author: :  Steven
 * @Date: 2020/2/13 16:56
 */
public class AjaxAuthenticationSuccessHandler  implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();

        responseBody.setStatus("200");
        responseBody.setMsg("Login Success! 登录成功");

        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

2.4、无权访问-AccessDeniedHandler

登录成功但无权访问
package com.grsoft.security.common;

import com.alibaba.fastjson.JSON;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Description: 登录成功但无权访问
 * @author: :  Steven
 * @Date: 2020/2/13 16:58
 */
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();
        responseBody.setStatus("300");
        responseBody.setMsg("Need Authorities! 登录成功但无权访问");
        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

2.5、退出登录-LogoutSuccessHandler

package com.grsoft.security.common;

import com.alibaba.fastjson.JSON;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();

        responseBody.setStatus("100");
        responseBody.setMsg("Logout Success!");

        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

3、用户认证相关

 判断授权有效性,用户有效性,在判断用户是否有效性,它依赖于UserDetailsService实例

3.1 实现UserDetails

package com.grsoft.security.authentication;

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

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;

public class SelfUserDetails implements UserDetails, Serializable {
    private String username;
    private String password;
    private Set<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    // 最重点Ⅰ
    @Override
    public String getPassword() {
        return this.password;
    }

    // 最重点Ⅱ
    @Override
    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

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

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

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

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

3.2实现UserDetailsService

package com.grsoft.security.authentication;

import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;

import java.util.HashSet;
import java.util.Set;

@Component
public class SelfUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //构建用户信息的逻辑(从数据库取用户信息)
        SelfUserDetails userInfo = new SelfUserDetails();
        userInfo.setUsername(username); 

        Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
        String encodePassword = md5PasswordEncoder.encodePassword("123", username); // 模拟从数据库中获取的密码原为 123
        userInfo.setPassword(encodePassword);

        Set authoritiesSet = new HashSet();
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN"); // 模拟从数据库中获取用户角色

        authoritiesSet.add(authority);
        userInfo.setAuthorities(authoritiesSet);

        return userInfo;
    }
}

4、前端交互-AuthenticationProvider

package com.grsoft.security.authentication;

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.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;

@Component
public class SelfAuthenticationProvider implements AuthenticationProvider {
    
    @Autowired
    SelfUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
               String userName = (String) authentication.getPrincipal(); // 这个获取表单输入中返回的用户名;
        String password = (String) authentication.getCredentials(); // 这个是表单中输入的密码;

        Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
        String encodePwd = md5PasswordEncoder.encodePassword(password, userName);

        //1.从数据库中取出用户的信息
        UserDetails userInfo = userDetailsService.loadUserByUsername(userName);

        //2.把Form输入的密码和数据库的密码对比
        if (!userInfo.getPassword().equals(encodePwd)) {
            throw new BadCredentialsException("用户名密码不正确,请重新登陆!");
        }

        return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
    }

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

5、登录拦截全局配置-WebSecurityConfigurerAdapter

package com.grsoft.security.common;

import com.grsoft.security.authentication.SelfAuthenticationProvider;
import com.grsoft.security.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Autowired
    AjaxAuthenticationEntryPoint authenticationEntryPoint;  //  未登陆时返回 JSON 格式的数据给前端(否则为 html)

    @Autowired
    AjaxAuthenticationSuccessHandler authenticationSuccessHandler;  // 登录成功返回的 JSON 格式数据给前端(否则为 html)

    @Autowired
    AjaxAuthenticationFailureHandler authenticationFailureHandler;  //  登录失败返回的 JSON 格式数据给前端(否则为 html)

    @Autowired
    AjaxLogoutSuccessHandler logoutSuccessHandler;  // 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)

    @Autowired
    AjaxAccessDeniedHandler accessDeniedHandler;    // 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)

    @Autowired
    SelfAuthenticationProvider provider; // 自定义安全认证

    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器

    /**
     * @Description: 配置Security的认证信息, 用户身份的管理者, 是认证的入口
     * @author: :  Steven
     * @Date: 2020/2/17 18:46
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //方式1
        // 加入自定义的安全认证
        auth.authenticationProvider(provider);

        //还有其他两种方式如下:
        //方式2
        //auth.inMemoryAuthentication().withUser("admin").password("admin").roles("USER");
        //方式3
        //auth.userDetailsService(dbUserDetailsService);
    }

    /**
     * @Description: 配置Security的认证策略
     * @author: :  Steven
     * @Date: 2020/2/17 18:46
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()// 禁用跨站攻击
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
                
                .and()
                .authorizeRequests()
//                .antMatchers("/login", "/authentication/form").permitAll() //自定义不进行认证的页面
                .anyRequest()
                .authenticated()// 其他 url 需要身份认证
                
                .and()
                .formLogin()  //开启登录
//                .loginPage("/myLogin.html")// 自定义登录页面
//                .loginProcessingUrl("/login.do")// 自定义登录controller
                .successHandler(authenticationSuccessHandler) // 登录成功
                .failureHandler(authenticationFailureHandler) // 登录失败
                .permitAll()
                
                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll();

        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
        //加入过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //指定忽略的静态资源
        web.ignoring().antMatchers("/static/**");
    }
}

HttpSecurity使用说明

方法

说明

openidLogin()

用于基于 OpenId 的验证

headers()

将安全标头添加到响应

cors()

配置跨域资源共享( CORS )

sessionManagement()

允许配置会话管理

portMapper()

允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443

jee()

配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理

x509()

配置基于x509的认证

rememberMe

允许配置“记住我”的验证

authorizeRequests()

允许基于使用HttpServletRequest限制访问

requestCache()

允许配置请求缓存

exceptionHandling()

允许配置错误处理

securityContext()

在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用

servletApi()

将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用

csrf()

添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用

logout()

添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”

anonymous()

允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”

formLogin()

指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面

oauth2Login()

根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证

requiresChannel()

配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射

httpBasic()

配置 Http Basic 验证

addFilterAt()

在指定的Filter类的位置添加过滤器

6、测试

在浏览器输入http://localhost:8080/index 输入错误的密码:

7、执行过程总结

登录:执行过程基本是这样的:a.Form登录login---->b.AuthenticationProvider.()返回Authentication对象----->c.根据上一步返回的结果判断进入AuthenticationSuccessHandler还是AuthenticationFailureHandler

发布了116 篇原创文章 · 获赞 14 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/allensandy/article/details/104297999