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;