Spring Boot + Spring Security + Vue实现前后端分离登录认证

本文参考地址:https://blog.csdn.net/I_am_Hutengfei/article/details/100561564/

上述地址中作者开发了后端的登录认证功能,但是对于普通的不涉及权限的前后端分离登录就略有不同,这里仅讲述与上述地址中作者描述不同的地方

LoginService.java
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
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 top.gerritchang.daily.menu.entity.UserEntity;
import top.gerritchang.daily.menu.mybatis.LoginMapper;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;

@Service
public class LoginService implements UserDetailsService {

    @Resource
    private SqlSessionTemplate sqlSessionTemplate;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LoginMapper loginMapper = sqlSessionTemplate.getMapper(LoginMapper.class);
        UserEntity userEntity = loginMapper.getUserByUName(username); //UserEntity是用户基本信息,后面调用的是自己的Mapper层查询用户的接口
        if (userEntity == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); //这个地方随便赋值一个角色就好了
        userEntity.setAuthorities(authorities);
        SecurityContextHolder.setContext(SecurityContextHolder.getContext());
        return userEntity;
    }
}

  UserEntity.java

import java.util.Collection;

public class UserEntity implements UserDetails {
    private String id;
    private String username;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

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

    public String getUsername() {
        return username;
    }

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

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

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

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

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

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

    public String getPassword() {
        return password;
    }

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

  CustomizeFilterInvocationSecurityMetadataSource.java

import java.util.Collection;

@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取请求地址
//        String requestUrl = ((FilterInvocation) o).getRequestUrl();
//        //查询具体某个接口的权限
//        List<SysPermission> permissionList =  sysPermissionService.selectListByPath(requestUrl);
//        if(permissionList == null || permissionList.size() == 0){
//            //请求路径没有配置权限,表明该请求接口可以任意访问
//            return null;
//        }
//        String[] attributes = new String[permissionList.size()];
        String[] attributes = new String[]{"ROLE_ADMIN"}; //这里和第一个文件的内容相同即可
//        for(int i = 0;i<permissionList.size();i++){
//            attributes[i] = permissionList.get(i).getPermissionCode();
//        }
        return SecurityConfig.createList(attributes);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

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

  SecurityConfiguration.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import top.gerritchang.daily.auth.*;
import top.gerritchang.daily.menu.service.LoginService;

import javax.annotation.Resource;

/**
 * 这个是前后端不分离项目中登录
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Resource
    private LoginService loginService;

    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Resource
    private MyAuthenticationFailHandler myAuthenticationFailHandler;

    @Resource
    private CustomizeLogoutSuccessHandler logoutSuccessHandler;

    @Resource
    private CustomizeAuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    @Resource
    private CustomizeAbstractSecurityInterceptor securityInterceptor;

    //访问决策管理器
    @Resource
    CustomizeAccessDecisionManager accessDecisionManager;

    //实现权限拦截
    @Resource
    CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.cors().and().csrf().disable();
        httpSecurity
                .cors()
                .and()
                .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(accessDecisionManager);//决策管理器
                        o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
                        return o;
                    }
                })
                .and().logout().logoutSuccessUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler)//登出成功处理逻辑
                .deleteCookies("JSESSIONID")
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenticationFailHandler)
                .permitAll()
                .and().exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .and().sessionManagement()
                .maximumSessions(1)//同一账号同时登录最大用户数
                .expiredSessionStrategy(sessionInformationExpiredStrategy);
        httpSecurity.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

  其他在SecurityConfiguration里面用到的类与引用地址给出的一致即可,部分名字可能有出入,以引用地址为主即可

这时,调用axios登录的地址就变成了上述代码段里配置的"/login",那么axios去登录的话需要这样写:

let params = new FormData();
params.append("username", username);
params.append("password", password);
let config = {
      headers: {"Content-Type": "application/x-www-form-urlencoded"}
};
let url = "http://localhost:8081/login";
axios.post(url, params, config)

  这时,虽然可以正常登录了,但是呢,当你访问其他的资源的时候会报角色错,角色就变成了"ROLE_ANONYMOUS"。这时我们需要在前端里修改一下,让axios请求的时候带上cookies即可,具体代码如下:

import axios from 'axios'
axios.defaults.withCredentials = true;

 

posted @ 2022-03-13 23:43  GerritChang  阅读(2025)  评论(0编辑  收藏  举报