前面我们了解了springboot与springsecurity的整合,也了解了springsecurity通过oauth2完成单点登录。这一节我们将看一下springsecurity在前后端分离项目中的使用,也就是其对vue界面的保护和管理。
1、环境约束
- idea2018.1
- maven3.6.1
2、前提约束
- 了解springboot与springsecurity的整合
https://www.jianshu.com/p/8ec3cd99bd26
3、操作步骤
3.1、创建前端项目
- 创建vue项目,假设名称为springsecurity-vue-ui
https://www.jianshu.com/p/644eb12a8174 - 在springsecurity-vue-ui/src文件夹下创建views文件夹
- 在springsecurity-vue-ui/src/views文件夹下创建index.vue
<template>
<div >
用户名: <input type="text" v-model="loginForm.username" />
<br><br>
密码: <input type="text" v-model="loginForm.password" />
<br><br>
<input type="button" value="登录" @click="login('loginForm')"/>
</div>
</template>
<script>
import axios from 'axios'
axios.defaults.withCredentials = true //跨域
axios.defaults.timeout = 10000
axios.defaults.headers.post['Content-Type'] = 'application/x-www=form-urlencoded'
export default {
data () {
return {
loginForm: {
username: 'zhangli',
password: '123456',
}
}
},
methods:{
login: function (){
let _this = this;
axios.post("/login?username="+this.loginForm.username+"&password="+this.loginForm.password)
.then(function (response) {
if(response.data.status="200")
{
window.location.href="/show"
}
})
.catch(function (reason) {
console.log(reason);
})
}
}
}
</script>
- 修改springsecurity-vue-ui/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Index from '../views/index/index'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
// 配置路由地址
{
path: '/index',
name: 'Index',
component: Index
}
]
})
- 修改springsecurity-vue-ui/config/index.js中的proxyTable节点:
proxyTable: {
'/login': {
target: 'http://localhost:8082',
'^/login':'/login'
},
'/show': {
target: 'http://localhost:8082',
'^/show':'/show'
},
'/logout': {
target: 'http://localhost:8082',
'^/logout':'/logout'
}
}
- 下载包、打包以及启动,执行以下命令:
cd springsecurity-vue-ui
# 下载包
cnpm install
# 打包
cnpm run build
# 启动
cnpm run start
- 访问http://localhost:8080/#/index,得到以下界面:
3.2、创建后端项目
- 创建一个springboot项目,假设名称为springsecurity-vue-server,pom.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<groupId>net.wanho</groupId>
<artifactId>springsecurity_vue_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurity_vue_server</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- 在application.yml中设置端口:
server:
port: 8082
- 在主启动类下创建UserInfo.java以记录用户信息
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class UserInfo implements UserDetails {
private User user;
private List<Role> roles;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (roles == null || roles.isEmpty()) {
return new ArrayList<>();
}
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.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;
}
public static class Role implements Serializable {
private long id;
private String name;
public Role(long id, String name) {
this.id = id;
this.name = name;
}
public Role() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class User implements Serializable {
private Long id;
private String username;
private String password;
public User(Long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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;
}
}
}
- 在主启动类下创建CorsConfig.java以完成跨域:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedHeaders("*")
.allowedOrigins("*");
}
}
- 在主启动类下创建MyUserDetailService.java以获取用户信息,注意,我们这边的用户信息是写死的。
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 java.util.ArrayList;
import java.util.List;
@Service
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo.User user = new UserInfo.User();
user.setUsername("zhangli");
user.setPassword("123456");
List<UserInfo.Role> list = new ArrayList<>();
UserInfo.Role role = new UserInfo.Role(1, "USER");
list.add(role);
UserInfo userDetail = new UserInfo();
userDetail.setRoles(list);
userDetail.setUser(user);
if (userDetail == null) {
throw new UsernameNotFoundException("Not found username:" + username);
}
return userDetail;
}
}
- 在主启动类下创建MyAuthenticationProvider.java以驱动MyUserDetailService.java
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 org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
@Resource
private UserDetailsService userDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();// 这个获取表单输入中返回的用户名;
String password = (String) authentication.getCredentials();// 这个是表单中输入的密码;
UserInfo userInfo = (UserInfo) userDetailService.loadUserByUsername(userName); // 这里调用我们的自己写的获取用户的方法;
if (userInfo == null) {
throw new BadCredentialsException("用户名不存在");
}
if (!userInfo.getPassword().equals(password)) {
throw new BadCredentialsException("密码不正确");
}
Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
- 在主启动类下创建WebSecurityConfig.java以设置web安全
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.method.configuration.EnableGlobalMethodSecurity;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
MyAuthenticationProvider authenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// 对登录注册要允许匿名访问;
.antMatchers("/login").permitAll()
// 其他的路径都要登录之后具备USER角色
.antMatchers("/**").hasRole("USER")
//这里配置的loginProcessingUrl为页面中对应表单的 action ,该请求为 post,并设置可匿名访问
.and().formLogin().loginProcessingUrl("/login").permitAll()
//登录成功后的返回结果
.successHandler(new AuthenticationSuccessHandlerImpl())
//登录失败后的返回结果
.failureHandler(new AuthenticationFailureHandlerImpl())
//这里配置的logoutUrl为登出接口,并设置可匿名访问
.and().logout().logoutUrl("/logout").permitAll()
//登出后的返回结果
.logoutSuccessHandler(new LogoutSuccessHandlerImpl())
//这里配置的为当未登录访问受保护资源时,返回json,并且让springsecurity自带的登录界面失效
.and().exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointHandler());
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
//定义登陆成功返回信息
private class AuthenticationSuccessHandlerImpl extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
UserInfo userInfo = (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
PrintWriter out = response.getWriter();
out.write("{\"status\":\"200\",\"msg\":\"登录成功\"}");
out.flush();
out.close();
}
}
//定义登出成功返回信息
private class LogoutSuccessHandlerImpl extends SimpleUrlLogoutSuccessHandler {
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"200\",\"msg\":\"登出成功\"}");
out.flush();
out.close();
}
}
//定义登陆失败返回信息
private class AuthenticationFailureHandlerImpl extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"100\",\"msg\":\"请检查用户名、密码或验证码是否正确\"}");
out.flush();
out.close();
}
}
public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.sendRedirect("http://localhost:8080/#/index");
}
}
}
- 在主启动下创建UserController.java以提供api
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/show")
public JSONObject show(){
JSONObject jsonObject = new JSONObject();
jsonObject.put("status","200");
jsonObject.put("data","this is the data");
return jsonObject;
}
}
- 启动springboot
3.3、测试
- 访问http://localhost:8080/#/index,点击“登录”,得到以下页面
注意,这里本应该是个页面,但作者只提供了api访问的响应结果 - 打开一个新的标签页,访问http://localhost:8080/#/logout,得到以下页面
- 再次刷新之前的http://localhost:8080/show,则又重新显示登录页面。
以上就是springsecurity对vue界面的保护和管理。