本文主要实现:
基于数据库的用户登录认证授权(基于内存的没研究)
基于cookie的remember me记住我的功能
先上代码,再说明:
maven:
其中验证码用的google code kaptcha,需要自己去maven仓库下载jar包install进自己的仓库,直接用maven配置是无法下载的
1
2
3
4
5
6
7
8
9
10
11
12 1<!-- 安全框架 Spring Security -->
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-security</artifactId>
5 </dependency>
6
7<dependency>
8 <groupId>com.google.code.kaptcha</groupId>
9 <artifactId>kaptcha</artifactId>
10 <version>2.3</version>
11 </dependency>
12
spring security需要自定义的类比较多,请耐心看完。。。
1、UserDetails
根据前端传过来的用户名,从数据库中查处对应数据,然后再封装成这个类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 1package com.example.demo.config.security;
2
3import com.example.demo.entity.UserEntity;
4import org.springframework.security.core.GrantedAuthority;
5import org.springframework.security.core.authority.SimpleGrantedAuthority;
6import org.springframework.security.core.userdetails.UserDetails;
7
8import java.util.ArrayList;
9import java.util.Collection;
10
11/**
12 * 功能描述:自定义spring security中,用于验证的用户实体类
13 *
14 * @author liuchaoyong
15 * @version 1.0
16 * @date 2019-02-21 21:38
17 */
18public class MyUserDetails implements UserDetails {
19
20 private UserEntity userEntity;
21
22 public MyUserDetails(UserEntity userEntity) {
23 this.userEntity = userEntity;
24 }
25
26 @Override
27 public Collection<? extends GrantedAuthority> getAuthorities() {
28 Collection<GrantedAuthority> authorities = new ArrayList<>();
29 userEntity.getRoleEntitySet().forEach(roleEntity -> authorities.add(new SimpleGrantedAuthority(roleEntity.getName())));
30 return authorities;
31 }
32
33 @Override
34 public String getPassword() {
35 return userEntity.getPassword();
36 }
37
38 @Override
39 public String getUsername() {
40 return userEntity.getUsername();
41 }
42
43 @Override
44 public boolean isAccountNonExpired() {
45 return true;
46 }
47
48 @Override
49 public boolean isAccountNonLocked() {
50 return userEntity.getIsLocked().equals((byte)0);
51 }
52
53 @Override
54 public boolean isCredentialsNonExpired() {
55 return true;
56 }
57
58 @Override
59 public boolean isEnabled() {
60 return userEntity.getIsEnable().equals((byte)1);
61 }
62}
63
64
2、UserDetailsService
用于返回刚才我们自己封装的实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 1package com.example.demo.config.security;
2
3import com.example.demo.entity.UserEntity;
4import com.example.demo.service.UserService;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.security.core.userdetails.UserDetails;
7import org.springframework.security.core.userdetails.UserDetailsService;
8import org.springframework.security.core.userdetails.UsernameNotFoundException;
9import org.springframework.stereotype.Component;
10
11/**
12 * 功能描述:自定义spring security中,用于验证的服务类
13 *
14 * @author liuchaoyong
15 * @version 1.0
16 * @date 2019-02-21 21:51
17 */
18@Component
19public class MyUserDetailsService implements UserDetailsService {
20
21 @Autowired
22 private UserService userService;
23
24 @Override
25 public UserDetails loadUserByUsername(String name)
26 throws UsernameNotFoundException {
27 UserEntity userEntity = userService.findByUsername(name);
28 if (userEntity == null) {
29 throw new UsernameNotFoundException("1");
30 }
31 //封装自定义UserDetails类
32 return new MyUserDetails(userEntity);
33 }
34
35}
36
37
3、AuthenticationSuccessHandler
用于自定义认证成功后的处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 1package com.example.demo.config.security;
2
3import org.springframework.security.core.Authentication;
4import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
5import org.springframework.stereotype.Component;
6
7import javax.servlet.ServletException;
8import javax.servlet.http.HttpServletRequest;
9import javax.servlet.http.HttpServletResponse;
10import java.io.IOException;
11
12/**
13 * 功能描述:认证成功处理器
14 *
15 * @author liuchaoyong
16 * @version 1.0
17 * @date 2019-02-22 23:34
18 */
19@Component
20public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
21
22 @Override
23 public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
24 response.getWriter().write("0");
25 response.setStatus(200);
26 }
27}
28
29
4、AuthenticationFailureHandler
用于自定义认证失败时的处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 1package com.example.demo.config.security;
2
3import org.springframework.security.core.AuthenticationException;
4import org.springframework.security.web.authentication.AuthenticationFailureHandler;
5import org.springframework.stereotype.Component;
6
7import javax.servlet.http.HttpServletRequest;
8import javax.servlet.http.HttpServletResponse;
9import java.io.IOException;
10
11/**
12 * 功能描述:认证失败处理器
13 *
14 * @author liuchaoyong
15 * @version 1.0
16 * @date 2019-02-23 15:20
17 */
18@Component
19public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
20
21 @Override
22 public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
23 //认证失败返回失败类型,返回200只表示请求成功。认证失败的提示交给前端处理
24 httpServletResponse.getWriter().write(e.getMessage());
25 httpServletResponse.setStatus(200);
26
27 }
28}
29
30
5、AuthenticationFilter
自定义过滤器,用于自定义验证,此处我只加了验证码验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 1package com.example.demo.config.security;
2
3import com.google.code.kaptcha.Constants;
4import org.apache.commons.lang3.StringUtils;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.security.authentication.BadCredentialsException;
7import org.springframework.stereotype.Component;
8import org.springframework.web.filter.OncePerRequestFilter;
9
10import javax.servlet.FilterChain;
11import javax.servlet.ServletException;
12import javax.servlet.http.HttpServletRequest;
13import javax.servlet.http.HttpServletResponse;
14import java.io.IOException;
15
16/**
17 * 功能描述:自定义过滤器---验证码验证
18 *
19 * @author liuchaoyong
20 * @version 1.0
21 * @date 2019-02-26 09:32
22 */
23@Component
24public class MyKaptchaFilter extends OncePerRequestFilter {
25
26 @Autowired
27 private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
28
29 @Override
30 protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {
31 if (StringUtils.equals(httpServletRequest.getRequestURI(), "/login")) {
32 Object kaptcha = httpServletRequest.getParameter("kaptcha");
33 Object kaptchaSession = httpServletRequest.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
34 if (null == kaptcha || null == kaptchaSession || !StringUtils.equals(kaptcha.toString(), kaptchaSession.toString())) {
35 myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,new BadCredentialsException("6"));
36 return;
37 }
38 }
39 filterChain.doFilter(httpServletRequest, httpServletResponse);
40 }
41}
42
43
6、AuthenticationProvider
认证逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 1package com.example.demo.config.security;
2
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.security.authentication.*;
5import org.springframework.security.core.Authentication;
6import org.springframework.security.core.AuthenticationException;
7import org.springframework.security.core.userdetails.UserDetails;
8import org.springframework.security.core.userdetails.UserDetailsService;
9import org.springframework.security.core.userdetails.UsernameNotFoundException;
10import org.springframework.stereotype.Component;
11
12/**
13 * 功能描述:认证
14 *
15 * @author liuchaoyong
16 * @version 1.0
17 * @date 2019-02-21 21:33
18 */
19@Component
20public class MyAuthenticationProvider implements AuthenticationProvider {
21
22 @Autowired
23 private UserDetailsService myUserDetailsService;
24
25 @Override
26 public Authentication authenticate(Authentication authenticate) throws AuthenticationException {
27 UsernamePasswordAuthenticationToken token
28 = (UsernamePasswordAuthenticationToken) authenticate;
29 String username = token.getName();
30 UserDetails userDetails = null;
31 if (username != null) {
32 userDetails = myUserDetailsService.loadUserByUsername(username);
33 }
34 if (userDetails == null) {
35 throw new UsernameNotFoundException("1");
36 } else if (!userDetails.isEnabled()) {
37 throw new DisabledException("2");
38 } else if (!userDetails.isAccountNonExpired()) {
39 throw new LockedException("3");
40 } else if (!userDetails.isAccountNonLocked()) {
41 throw new LockedException("4");
42 } else if (!userDetails.isCredentialsNonExpired()) {
43 throw new LockedException("5");
44 }
45
46 String password = userDetails.getPassword();
47 //与authentication里面的credentials相比较
48 if (!password.equals(token.getCredentials())) {
49 throw new BadCredentialsException("1");
50 }
51 //授权
52 return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
53 }
54
55 @Override
56 public boolean supports(Class<?> authentication) {
57 //返回true后才会执行上面的authenticate方法,这步能确保authentication能正确转换类型
58 return UsernamePasswordAuthenticationToken.class.equals(authentication);
59 }
60
61}
62
63
7、WebSecurityConfigAdapter
安全控制中心,用于配置spring security
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 1package com.example.demo.config.security;
2
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6import org.springframework.security.authentication.AuthenticationProvider;
7import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
8import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9import org.springframework.security.config.annotation.web.builders.WebSecurity;
10import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
11import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
12import org.springframework.security.core.userdetails.UserDetailsService;
13import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
14import org.springframework.security.crypto.password.PasswordEncoder;
15import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
16
17/**
18 *
19 * 功能描述: 安全控制中心
20 *
21 *
22 * @author liuchaoyong
23 * @date 2019-02-21 20:34
24 * @version 1.0
25 */
26@Configuration
27@EnableWebSecurity
28public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
29
30 @Autowired
31 private UserDetailsService myUserDetailsService;
32
33 @Autowired
34 private AuthenticationProvider myAuthenticationProvider;
35
36 @Autowired
37 private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
38
39 @Autowired
40 private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
41
42 @Autowired
43 private MyKaptchaFilter myKaptchaFilter;
44
45 @Override
46 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
47 auth.userDetailsService(myUserDetailsService);
48 auth.authenticationProvider(myAuthenticationProvider);
49 }
50
51 @Bean
52 public PasswordEncoder getPasswordEncoder(){
53 return new BCryptPasswordEncoder();
54 }
55
56 @Override
57 protected void configure(HttpSecurity http) throws Exception {
58 //权限控制
59 http.authorizeRequests()
60 .antMatchers("/kaptcha","/druid/**").permitAll()
61 .anyRequest().authenticated()
62 .and()
63 //添加过滤器
64 .addFilterBefore(myKaptchaFilter, UsernamePasswordAuthenticationFilter.class)
65 .formLogin()
66 //成功处理
67 .successHandler(myAuthenticationSuccessHandler)
68 //失败处理
69 .failureHandler(myAuthenticationFailureHandler)
70 //登录页
71 .loginPage("/view/login.html")
72 //登录请求
73 .loginProcessingUrl("/login")
74 .permitAll()
75 .and()
76 .logout()
77 .logoutUrl("/logout")
78 .and()
79 //记住我
80 .rememberMe()
81 //有效期
82 .tokenValiditySeconds(60*60*24*30)
83 .and()
84 .csrf()
85 .disable();
86 }
87
88 @Override
89 public void configure(WebSecurity web){
90 web.ignoring().antMatchers("/css/**","/js/**");
91 }
92}
93
94
前端页面:
前端使用的vue + element-ui + axios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="UTF-8">
5 <!-- import CSS -->
6 <link rel="stylesheet" href="/css/element-ui.css">
7 <!-- import Vue before Element -->
8 <script src="/js/vue.js"></script>
9 <!-- import JavaScript -->
10 <script src="/js/element-ui.js"></script>
11 <script src="/js/axios.min.js"></script>
12</head>
13<body>
14<div id="app">
15 <el-container>
16 <el-header>
17
18 </el-header>
19 <el-main>
20 <el-row>
21 <el-col :span="8" :offset="8">
22 <el-input v-model.trim="username" placeholder="用户名"></el-input>
23 </el-col>
24 </el-row>
25 <el-row>
26 <el-col :span="8" :offset="8">
27 <el-input v-model.trim="password" type="password" placeholder="密码"></el-input>
28 </el-col>
29 </el-row>
30 <el-row>
31 <el-col :span="5" :offset="8">
32 <el-input v-model.trim="kaptcha" placeholder="验证码"></el-input>
33 </el-col>
34 <el-col :span="3">
35 <img src="/kaptcha" alt=""/>
36 </el-col>
37 </el-row>
38 <el-row>
39 <el-col :span="8" :offset="8">
40 <el-checkbox v-model.trim="rememberMe">记住我</el-checkbox>
41 </el-col>
42 </el-row>
43 <el-row>
44 <el-col :span="8" :offset="8">
45 <el-button type="primary" round @click="login()">登录</el-button>
46 </el-col>
47 </el-row>
48 </el-main>
49 <el-footer>
50
51 </el-footer>
52 </el-container>
53</div>
54</body>
55<script>
56 new Vue({
57 el: '#app',
58 data: function () {
59 return {
60 username: 'root',
61 password: '123456',
62 kaptcha: '',
63 rememberMe: false
64 }
65 },
66 methods: {
67 login() {
68 let urlSearchParams = new URLSearchParams();
69 urlSearchParams.append("username", this.username);
70 urlSearchParams.append("password", this.password);
71 urlSearchParams.append("kaptcha", this.kaptcha);
72 urlSearchParams.append("remember-me", this.rememberMe);
73 axios.post('/login', urlSearchParams).then(response => {
74 let data = response.data;
75 if (data === 0) {
76 window.location.href = "/view/user/index.html";
77 } else if (data === 1) {
78 this.$message.error("用户名或密码不正确");
79 } else if (data === 2) {
80 this.$message.error("账号已被禁用")
81 } else if (data === 3) {
82 this.$message.error("账号已过期")
83 } else if (data === 4) {
84 this.$message.error("账号已被锁定")
85 } else if (data === 5) {
86 this.$message.error("凭证已过期")
87 }else if(data === 6){
88 this.$message.error("验证码错误")
89 } else {
90 this.$message.error("异常")
91 }
92 });
93 }
94
95
96 }
97 })
98</script>
99</html>
100
这里说明一下传参
spring security中验证用户名密码的过滤器是UsernamePasswordAuthenticationFilter,源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 1//
2// Source code recreated from a .class file by IntelliJ IDEA
3// (powered by Fernflower decompiler)
4//
5
6package org.springframework.security.web.authentication;
7
8import javax.servlet.http.HttpServletRequest;
9import javax.servlet.http.HttpServletResponse;
10import org.springframework.security.authentication.AuthenticationServiceException;
11import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
12import org.springframework.security.core.Authentication;
13import org.springframework.security.core.AuthenticationException;
14import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
15import org.springframework.util.Assert;
16
17public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
18 public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
19 public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
20 private String usernameParameter = "username";
21 private String passwordParameter = "password";
22 private boolean postOnly = true;
23
24 public UsernamePasswordAuthenticationFilter() {
25 super(new AntPathRequestMatcher("/login", "POST"));
26 }
27
28 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
29 if (this.postOnly && !request.getMethod().equals("POST")) {
30 throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
31 } else {
32 String username = this.obtainUsername(request);
33 String password = this.obtainPassword(request);
34 if (username == null) {
35 username = "";
36 }
37
38 if (password == null) {
39 password = "";
40 }
41
42 username = username.trim();
43 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
44 this.setDetails(request, authRequest);
45 return this.getAuthenticationManager().authenticate(authRequest);
46 }
47 }
48
49 protected String obtainPassword(HttpServletRequest request) {
50 return request.getParameter(this.passwordParameter);
51 }
52
53 protected String obtainUsername(HttpServletRequest request) {
54 return request.getParameter(this.usernameParameter);
55 }
56
57 protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
58 authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
59 }
60
61 public void setUsernameParameter(String usernameParameter) {
62 Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
63 this.usernameParameter = usernameParameter;
64 }
65
66 public void setPasswordParameter(String passwordParameter) {
67 Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
68 this.passwordParameter = passwordParameter;
69 }
70
71 public void setPostOnly(boolean postOnly) {
72 this.postOnly = postOnly;
73 }
74
75 public final String getUsernameParameter() {
76 return this.usernameParameter;
77 }
78
79 public final String getPasswordParameter() {
80 return this.passwordParameter;
81 }
82}
83
84
可以看到,spring security是通过request.getParameter()接收的,只能接收Content-Type:application/x-www-form-urlencoded格式的数据,所以前端需要使用UrlSearchParams格式化参数
还有,前端传的“记住我”的参数的key一定是remember-me,这是spring security写死的。
静态资源结构:
如果有什么问题或者有什么可以简化的地方,可以在下面评论