springboot整合spring security

释放双眼,带上耳机,听听看~!

本文主要实现:

基于数据库的用户登录认证授权(基于内存的没研究)

基于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写死的。

静态资源结构:

springboot整合spring security

如果有什么问题或者有什么可以简化的地方,可以在下面评论

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C/C++内存泄漏及检测

2022-1-11 12:36:11

安全经验

Apache Software Foundation 发布 2019 年安全报告

2020-2-1 11:12:22

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索