package kmhc;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
@Profile("dev")
public class CORSFilter implements Filter {
@SuppressWarnings("unused")
private FilterConfig config;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");
if ("OPTIONS".equalsIgnoreCase(httpServletRequest.getMethod())) {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(request, response);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
config = filterConfig;
}
}
/api/users
这样一个接口,对这个接口发送post请求并填写必要的信息即可完成一个用户的创建import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
import axios from 'axios'
import VueAxios from 'vue-axios'
// 使用Vue.use的方式装载组件可以不破坏Vue的原型属性
Vue.use(VueAxios, axios)
Vue.config.productionTip = false
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
<template>
<v-container fluid>
<v-row justify="center">
<v-col sm="6" md="4" lg="3">
<v-card class="mx-auto">
<v-img class="white--text align-end" height="200px" src="../assets/user/top.jpeg">
<v-card-title>Registerv-card-title>
v-img>
<v-card-text>
<v-form ref="registerForm" v-model="valid" lazy-validation autocomplete="off">
<v-row>
<v-col>
<v-text-field v-model="form.username" :rules="rules.usernameRules" label="用户名">v-text-field>
v-col>
v-row>
<v-row>
<v-col>
<v-text-field v-model="form.password" :rules="rules.passwordRules" label="密码" :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'" @click:append="show = !show" :type="show ? 'text': 'password'">v-text-field>
v-col>
v-row>
<v-row>
<v-col>
<v-text-field v-model="form.passwordConfirm" :rules="[rules.passwordConfirmRules.required, rules.passwordConfirmRules.equal(form.password, form.passwordConfirm)]" label="确认密码" :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'" @click:append="show = !show" :type="show ? 'text': 'password'">v-text-field>
v-col>
v-row>
v-form>
v-card-text>
<v-card-actions>
<v-spacer>v-spacer>
<v-btn :disabled="!valid" color="primary" @click="register()">Registerv-btn>
v-card-actions>
v-card>
v-col>
v-row>
<v-snackbar
v-model="snackbar"
>
用户名已存在!
<template v-slot:action="{ attrs }">
<v-btn
color="pink"
text
v-bind="attrs"
@click="snackbar = false"
>
Close
v-btn>
template>
v-snackbar>
v-container>
template>
<script>
export default {
name: 'Register',
data: () => ({
// 用于验证表单内容填写是否正确
valid: true,
// 用于控制密码是否以可见的形式显示
show: false,
// 用于控制消息条是否显示
snackbar: false,
// 表单填写的内容
form: {
username: '',
password: '',
passwordConfirm: ''
},
// 验证规则
rules: {
usernameRules: [
v => !!v || '用户名不能为空',
v => ((v || '').length >= 1 && (v || '').length <= 20) || '用户名必须在1-20个字符以内'
],
passwordRules: [
v => !!v || '密码不能为空',
v => ((v || '').length >= 6 && (v || '').length <= 30) || '密码必须在6-30个字符以内'
],
passwordConfirmRules: {
required: v => !!v || '请输入确认密码',
equal: (password, passwordConfirm) => password === passwordConfirm || '两次密码不一致'
}
}
}),
methods: {
// 注册功能
register () {
// 点击注册按钮时首先验证表单填写的正确性
const valid = this.$refs.registerForm.validate()
if (valid) {
// 用vue-axios发送post请求
this.axios.post('http://127.0.0.1:9001/api/users', {
username: this.form.username,
password: this.form.password
}).then(() => {
// 注册成功,跳转到登录页面
this.$router.push({
name: 'Login'
})
}).catch((error) => {
// 发生请求错误,此处错误可能是因为用户名已存在,也有可能是提交的数据不符合要求,也有可能是服务器内部错误
// 当返回的状态码是409时,说明用户名已存在
console.log(error)
// 显示错误消息
this.snackbar = true
})
}
}
}
}
script>
select * from users where username = "test1";
查询是否真正成功package kmhc.api;
import java.util.Optional;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import kmhc.domain.user.User;
import kmhc.form.LoginForm;
import kmhc.repository.user.UserRepository;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserRepository userRepo;
@Autowired
private RepositoryEntityLinks repositoryEntityLinks;
@PostMapping(path = "/api/login")
public ResponseEntity<EntityModel<User>> login(@RequestBody @Valid LoginForm form, HttpServletRequest request) throws ServletException {
String username = form.getUsername();
String password = form.getPassword();
try {
// try login
request.login(username, password);
} catch (ServletException servletException) {
throw servletException;
}
// log authentication
log.info(username + " is authenticated!");
// return the user resource
Optional<User> optional = userRepo.findByUsername(username);
if (optional.isPresent()) {
// get the resource url
User user = optional.get();
Link link = repositoryEntityLinks.linkToItemResource(User.class, user.getId());
EntityModel<User> userEntity = EntityModel.of(user, link);
return new ResponseEntity<>(userEntity, HttpStatus.OK);
}
throw new UsernameNotFoundException("User '" + username +"' not found!");
}
}
/api/login
接口的POST请求package kmhc.form;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
@Data
public class LoginForm {
@NotNull
private String username;
@Size(min = 6, message = "password must be at least 6 characters!")
private String password;
}
/api/login
接口@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/login").permitAll()
.antMatchers(HttpMethod.POST, "/api/users").permitAll()
.antMatchers(HttpMethod.GET, "/api/users/{username}").permitAll()
.antMatchers("/api/users/{username}").authenticated()
.antMatchers(HttpMethod.GET, "/api/users").hasRole("ADMIN")
.antMatchers("/api/authorities/**").hasRole("ADMIN")
.antMatchers("/api/groups/**").hasRole("ADMIN")
.antMatchers("/api/groupAuthorities/**").hasRole("ADMIN")
.and()
.formLogin().disable()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
<template>
<v-container fluid>
<v-row justify="center">
<v-col sm="6" md="4" lg="3">
<v-card class="mx-auto">
<v-img class="white--text align-end" height="200px" src="../assets/user/top.jpeg">
<v-card-title>Loginv-card-title>
v-img>
<v-card-text>
<v-form ref="loginForm" v-model="valid" lazy-validation autocomplete="off">
<v-row>
<v-col>
<v-text-field v-model="form.username" :rules="rules.usernameRules" label="用户名">v-text-field>
v-col>
v-row>
<v-row>
<v-col>
<v-text-field v-model="form.password" :rules="rules.passwordRules" label="密码" :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'" @click:append="show = !show" :type="show ? 'text': 'password'">v-text-field>
v-col>
v-row>
v-form>
v-card-text>
<v-card-actions>
<v-spacer>v-spacer>
<v-btn :disabled="!valid" color="primary" @click="login()">Loginv-btn>
v-card-actions>
v-card>
v-col>
v-row>
<v-snackbar
v-model="snackbar"
>
用户名或密码不正确!
<template v-slot:action="{ attrs }">
<v-btn
color="pink"
text
v-bind="attrs"
@click="snackbar = false"
>
Close
v-btn>
template>
v-snackbar>
v-container>
template>
<script>
export default {
name: 'Login',
data: () => ({
// 用于验证表单内容填写是否正确
valid: true,
// 用于控制密码是否以可见的形式显示
show: false,
// 用于控制消息条是否显示
snackbar: false,
// 表单填写的内容
form: {
username: '',
password: ''
},
// 验证规则
rules: {
usernameRules: [
v => !!v || '用户名不能为空',
v => ((v || '').length >= 1 && (v || '').length <= 20) || '用户名必须在1-20个字符以内'
],
passwordRules: [
v => !!v || '密码不能为空',
v => ((v || '').length >= 6 && (v || '').length <= 30) || '密码必须在6-30个字符以内'
]
}
}),
methods: {
// 登录功能
login () {
// 首先验证表单填写的正确性
const valid = this.$refs.loginForm.validate()
if (valid) {
// 表单填写无误,向login接口发送post请求
this.axios.post('http://127.0.0.1:9001/api/login', {
username: this.form.username,
password: this.form.password
}).then(response => {
// 验证成功,获取用户信息
console.log(response)
}).catch(error => {
console.log(error)
// 用户认证错误,显示消息条
this.snackbar = true
})
}
}
}
}
script>
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 代表用户自身,最终不一定会以这种方式呈现
user: {
id: null,
username: '',
password: '',
enabled: true,
firstName: '',
lastName: '',
gender: 0,
phone: '',
email: '',
icon: '',
birthday: new Date().toISOString().substr(0, 10),
joinedDate: new Date().toISOString().substr(0, 10),
// 和用户相关的一些链接,比如用户自身的描述
_links: []
}
},
mutations: {
// 登录后设置user
setUser (state, user) {
state.user = user
},
// 注销后清除user
removeUser (state) {
state.user = {
id: null,
username: '',
password: '',
enabled: true,
firstName: '',
lastName: '',
gender: 0,
phone: '',
email: '',
icon: '',
birthday: new Date().toISOString().substr(0, 10),
joinedDate: new Date().toISOString().substr(0, 10),
// 和用户相关的一些链接,比如用户自身的描述
_links: []
}
}
},
actions: {
},
modules: {
}
})
login () {
// 首先验证表单填写的正确性
const valid = this.$refs.loginForm.validate()
if (valid) {
// 表单填写无误,向login接口发送post请求
this.axios.post('http://127.0.0.1:9001/api/login', {
username: this.form.username,
password: this.form.password
}).then(response => {
// 验证成功,获取用户信息
console.log(response)
// 设置全局user
this.$store.commit('setUser', response.data)
// 回到主页
this.$router.push({
name: 'Home'
})
}).catch(error => {
console.log(error)
// 用户认证错误,显示消息条
this.snackbar = true
})
}
}
至此,简单的注册、登录和注销功能已经实现,下一节会详细讲解如何用oauth2去实现用户的登录注册,返回token等