Skip to content

Commit 6143056

Browse files
zihongzihong
authored andcommitted
SpringBoot2集成SpringSecurity实现路由权限认证
1 parent fb63a49 commit 6143056

File tree

18 files changed

+615
-0
lines changed

18 files changed

+615
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
HELP.md
2+
.gradle
3+
/build/
4+
!gradle/wrapper/gradle-wrapper.jar
5+
6+
### STS ###
7+
.apt_generated
8+
.classpath
9+
.factorypath
10+
.project
11+
.settings
12+
.springBeans
13+
.sts4-cache
14+
15+
### IntelliJ IDEA ###
16+
.idea
17+
*.iws
18+
*.iml
19+
*.ipr
20+
/out/
21+
22+
### NetBeans ###
23+
/nbproject/private/
24+
/nbbuild/
25+
/dist/
26+
/nbdist/
27+
/.nb-gradle/
28+
29+
### VS Code ###
30+
.vscode/
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
### SpringBoot2 集成SpringSecurity资源访问权限限制
2+
3+
在日常开发中,难免会遇到权限相关的需求,关于权限处理这一块,用的比较多的安全框架分别是Shiro和Spring全家桶中的
4+
Spring-Security,之前已经使用过Shiro,对它也有一定的了解,下面是对Spring-Security学习做的记录。
5+
6+
#### 概述
7+
Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
8+
9+
#### 路由资源访问认证
10+
先用一个比较简单的基于角色访问权限的小例子入门
11+
12+
导入相关jar包,SpringBoot-Utils模块是我常用的一些公共类
13+
```text
14+
compile project(':SpringBoot-Utils')
15+
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.0.5.RELEASE'
16+
compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.0.5.RELEASE'
17+
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE'
18+
```
19+
配置Security
20+
```java
21+
@Configuration
22+
@EnableWebSecurity
23+
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
24+
@Autowired
25+
private AccessDeniedHandler accessDeniedHandler;
26+
27+
@Bean
28+
@Override
29+
protected UserDetailsService userDetailsService() {
30+
//直接建两个用户存在内存中,生产环境可以从数据库中读取,对应管理器JdbcUserDetailsManager
31+
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
32+
// 创建两个用户
33+
//通过密码的前缀区分编码方式,推荐,这种加密方式很好的利用了委托者模式,使得程序可以使用多种加密方式,并且会自动
34+
//根据前缀找到对应的密码编译器处理。
35+
manager.createUser(User.withUsername("guest").password("{bcrypt}" +
36+
new BCryptPasswordEncoder().encode("123456")).roles("USER").build());
37+
manager.createUser(User.withUsername("root").password("{sha256}" +
38+
new StandardPasswordEncoder().encode("666666"))
39+
.roles("ADMIN", "USER").build());
40+
return manager;
41+
}
42+
43+
44+
@Override
45+
@Bean
46+
public AuthenticationManager authenticationManagerBean() throws Exception {
47+
return super.authenticationManagerBean();
48+
}
49+
50+
/**
51+
* 支持多种编码,通过密码的前缀区分编码方式,推荐
52+
*
53+
* @return the password encoder
54+
*/
55+
@Bean
56+
PasswordEncoder passwordEncoder() {
57+
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
58+
}
59+
60+
@Override
61+
protected void configure(HttpSecurity http) throws Exception {
62+
// @formatter:on
63+
http.authorizeRequests()
64+
.antMatchers("/css/**", "/js/**", "/fonts/**").permitAll() // 允许访问资源
65+
.antMatchers("/", "/home", "/about", "/login").permitAll() //允许访问这三个路由
66+
.antMatchers("/admin/**").hasAnyRole("ADMIN") // 满足该条件下的路由需要ROLE_ADMIN的角色
67+
.antMatchers("/user/**").hasAnyRole("USER") // 满足该条件下的路由需要ROLE_USER的角色
68+
.anyRequest().authenticated()
69+
.and()
70+
.formLogin()
71+
.loginPage("/login")
72+
.permitAll()
73+
.and()
74+
.logout()
75+
.permitAll()
76+
.and()
77+
.exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()
78+
.csrf().disable();
79+
// @formatter:off
80+
}
81+
}
82+
```
83+
上面的代码主要配置了两个用户,分别是root和guest,并分别设置了角色,接着配置静态资源、登录页、首页允许访问,
84+
部分路由指定特定角色可以访问,并配置权限拒绝访问异常处理器和跨越无效。
85+
86+
权限拒绝访问处理器,配置跳转到特定页面
87+
```java
88+
@Component
89+
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
90+
91+
private static Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
92+
93+
@Override
94+
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
95+
Authentication auth
96+
= SecurityContextHolder.getContext().getAuthentication();
97+
98+
if (auth != null) {
99+
logger.info("User '" + auth.getName()
100+
+ "' attempted to access the protected URL: "
101+
+ httpServletRequest.getRequestURI());
102+
}
103+
104+
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403");
105+
}
106+
}
107+
```
108+
下面访问,/admin,会自动跳转到/login登录页,使用root账号登录,在访问/admin,会发现访问到了。
109+
![](http://ww1.sinaimg.cn/large/006mOQRagy1g31yorkhxzj30um0a50tl.jpg)
110+
接着在注销,使用guest的账号登录,会发现自动跳转到了403页面,也就是访问被拒绝了。
111+
![](http://ww1.sinaimg.cn/large/006mOQRagy1g31yqg9ku8j30lm08574s.jpg)
112+
原因是guest只配置Role为USER,而访问/admin需要有ADMIN的角色。
113+
到这里,简单的通过角色限制资源访问以实现了,后面如果真是有需要要用到,可自行改造。
114+
比如账号的管理可以从内存移至数据库中等等。
115+
116+
#### 填坑记录
117+
最后,在记录几处采坑的地方:
118+
* Security的配置中,有个坑,需要自己配置passwordEncoder,否则,
119+
登录会报java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"。
120+
简单的了解下PasswordEncoder:
121+
根据官方的定义,Spring Security的PasswordEncoder接口用于执行密码的单向转换,以便安全地存储密码。
122+
而DelegatingPasswordEncoder和NoOpPasswordEncoder都是PasswordEncoder接口的实现类,简单来说就是现在数据库存储的密码基本都是经过编码的,而决定如何编码以及判断未编码的字符序列和编码后的字符串是否匹配就是PassswordEncoder的责任.
123+
随着安全要求的提高,NoOpPasswordEncoder已经不被推荐了,如果你未自行加密,那么他存储的将是明文,并且当你因为加密算法安全性不够,需要更改时,将会变得非常棘手.
124+
而DelegatingPasswordEncoder,他是一个委派密码编译器,会根据不同的加密算法前缀标识委派给不同的密码编译器处理,
125+
使得程序可以同时存在好几种加密算法,所以这里推荐配置使用DelegatingPasswordEncoder,这里仅仅是提及一下,若想深入了解PassEncoder可在自行Google。
126+
```java
127+
/**
128+
* 支持多种编码,通过密码的前缀区分编码方式,推荐
129+
* 对应的密码需加上加密算法标识,如:{bcrypt}、{MD5}..........
130+
* @return the password encoder
131+
*/
132+
@Bean
133+
PasswordEncoder passwordEncoder() {
134+
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
135+
}
136+
```
137+
* 模板页面标签不生效,sec:authentication,原因可能如下:
138+
thymeleaf-extras-springsecurity4 Jar包未导入,或者版本与Security版本不匹配
139+
官方描述如下:
140+
```text
141+
This is a thymeleaf extras module, not a part of the Thymeleaf core (and as such following its own versioning schema), but fully supported by the Thymeleaf team.
142+
This repository contains 3 projects:
143+
thymeleaf-extras-springsecurity3 for integration with Spring Security 3.x
144+
thymeleaf-extras-springsecurity4 for integration with Spring Security 4.x
145+
thymeleaf-extras-springsecurity5 for integration with Spring Security 5.x
146+
Current versions:
147+
Version 3.0.4.RELEASE - for Thymeleaf 3.0 (requires Thymeleaf 3.0.10+)
148+
Version 2.1.3.RELEASE - for Thymeleaf 2.1 (requires Thymeleaf 2.1.2+)
149+
```
150+
按官方描述,我用的是Spring Security5.X的包,应该使用thymeleaf-extras-springsecurity5的包,但它确不生效。
151+
无奈之下尝试使用thymeleaf-extras-springsecurity4的包,便可以了,这里建议5.0.X的还是用
152+
thymeleaf-extras-springsecurity4的包,毕竟亲测生效了。
153+
154+
项目源码地址:
155+
https://github.com/liaozihong/SpringBoot-Learning/tree/master/SpringBoot-SpringSecurity
156+
157+
158+
参考链接:
159+
https://docs.spring.io/spring-security/site/docs/5.2.0.BUILD-SNAPSHOT/reference/htmlsingle/
160+
https://blog.csdn.net/alinyua/article/details/80219500
161+
https://juejin.im/entry/5a45fe8e6fb9a045132b0658
162+
https://segmentfault.com/a/1190000015191298
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dependencies {
2+
compile project(':SpringBoot-Utils')
3+
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.0.5.RELEASE'
4+
compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.0.5.RELEASE'
5+
compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE'
6+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.dashuai.learning.springsecurity;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class SpringSecurityApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(SpringSecurityApplication.class, args);
11+
}
12+
13+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.dashuai.learning.springsecurity.config;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.security.access.AccessDeniedException;
6+
import org.springframework.security.core.Authentication;
7+
import org.springframework.security.core.context.SecurityContextHolder;
8+
import org.springframework.security.web.access.AccessDeniedHandler;
9+
import org.springframework.stereotype.Component;
10+
11+
import javax.servlet.ServletException;
12+
import javax.servlet.http.HttpServletRequest;
13+
import javax.servlet.http.HttpServletResponse;
14+
import java.io.IOException;
15+
16+
/**
17+
* Custom access denied handler
18+
* <p/>
19+
* Created in 2019.05.15
20+
* <p/>
21+
*
22+
* @author Liaozihong
23+
*/
24+
@Component
25+
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
26+
27+
private static Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
28+
29+
@Override
30+
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
31+
Authentication auth
32+
= SecurityContextHolder.getContext().getAuthentication();
33+
34+
if (auth != null) {
35+
logger.info("User '" + auth.getName()
36+
+ "' attempted to access the protected URL: "
37+
+ httpServletRequest.getRequestURI());
38+
}
39+
40+
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403");
41+
42+
43+
}
44+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.dashuai.learning.springsecurity.config;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
5+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6+
7+
/**
8+
* Mvc config
9+
* <p/>
10+
* Created in 2019.05.14
11+
* <p/>
12+
*
13+
* @author Liaozihong
14+
*/
15+
@Configuration
16+
public class MvcConfig implements WebMvcConfigurer {
17+
18+
@Override
19+
public void addViewControllers(ViewControllerRegistry registry) {
20+
21+
registry.addViewController("/403").setViewName("error/403");
22+
registry.addViewController("/about").setViewName("about");
23+
registry.addViewController("/user").setViewName("user");
24+
registry.addViewController("/admin").setViewName("admin");
25+
registry.addViewController("/home").setViewName("home");
26+
registry.addViewController("/").setViewName("home");
27+
registry.addViewController("/hello").setViewName("hello");
28+
registry.addViewController("/login").setViewName("login");
29+
}
30+
31+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.dashuai.learning.springsecurity.config;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.security.authentication.AuthenticationManager;
7+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10+
import org.springframework.security.core.userdetails.User;
11+
import org.springframework.security.core.userdetails.UserDetailsService;
12+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
13+
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
14+
import org.springframework.security.crypto.password.PasswordEncoder;
15+
import org.springframework.security.crypto.password.StandardPasswordEncoder;
16+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
17+
import org.springframework.security.web.access.AccessDeniedHandler;
18+
19+
/**
20+
* Security configuration
21+
* <p/>
22+
* Created in 2019.05.14
23+
* <p/>
24+
*
25+
* @author Liaozihong
26+
*/
27+
@Configuration
28+
@EnableWebSecurity
29+
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
30+
@Autowired
31+
private AccessDeniedHandler accessDeniedHandler;
32+
33+
@Bean
34+
@Override
35+
protected UserDetailsService userDetailsService() {
36+
//直接建两个用户存在内存中,生产环境可以从数据库中读取,对应管理器JdbcUserDetailsManager
37+
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
38+
// 创建两个用户
39+
//通过密码的前缀区分编码方式,推荐,这种加密方式很好的利用了委托者模式,使得程序可以使用多种加密方式,并且会自动
40+
//根据前缀找到对应的密码编译器处理。
41+
manager.createUser(User.withUsername("guest").password("{bcrypt}" +
42+
new BCryptPasswordEncoder().encode("123456")).roles("USER").build());
43+
manager.createUser(User.withUsername("root").password("{sha256}" +
44+
new StandardPasswordEncoder().encode("666666"))
45+
.roles("ADMIN", "USER").build());
46+
return manager;
47+
}
48+
49+
50+
@Override
51+
@Bean
52+
public AuthenticationManager authenticationManagerBean() throws Exception {
53+
return super.authenticationManagerBean();
54+
}
55+
56+
/**
57+
* 支持多种编码,通过密码的前缀区分编码方式,推荐
58+
*
59+
* @return the password encoder
60+
*/
61+
@Bean
62+
PasswordEncoder passwordEncoder() {
63+
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
64+
}
65+
66+
@Override
67+
protected void configure(HttpSecurity http) throws Exception {
68+
// @formatter:on
69+
http.authorizeRequests()
70+
.antMatchers("/css/**", "/js/**", "/fonts/**").permitAll() // 允许访问资源
71+
.antMatchers("/", "/home", "/about", "/login").permitAll() //允许访问这三个路由
72+
.antMatchers("/admin/**").hasAnyRole("ADMIN") // 满足该条件下的路由需要ROLE_ADMIN的角色
73+
.antMatchers("/user/**").hasAnyRole("USER") // 满足该条件下的路由需要ROLE_USER的角色
74+
.anyRequest().authenticated()
75+
.and()
76+
.formLogin()
77+
.loginPage("/login")
78+
.permitAll()
79+
.and()
80+
.logout()
81+
.permitAll()
82+
.and()
83+
.exceptionHandling().accessDeniedHandler(accessDeniedHandler).and()
84+
.csrf().disable();
85+
// @formatter:off
86+
}
87+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
server:
2+
port: 8080
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
h1{
2+
color:#0000FF;
3+
}
4+
5+
h2{
6+
color:#FF0000;
7+
}
8+
9+
footer{
10+
margin-top:60px;
11+
}

0 commit comments

Comments
 (0)