Spring Sectury 的简单示例

概述

展示Spring Security中基于角色的登录。也就是说,根据其角色登录以后重定向到不同的url。
一般来说,我们需要自定义一个SuccessHandler 来根据用户角色处理登录用户的重定向到对应的url。
这个功能在Spring Security 里面已经提供了。
SimpleUrlAuthenticationSuccessHandler 含有常用的successhandler的常用逻辑。
我们仅需要拓展它,实现我们自己的逻辑即可。

版本

  • Spring 4.1.6.RELEASE
  • Spring Security 4.0.1.RELEASE
  • Maven 3
  • JDK 1.7

配置实现

项目结构

pom 依赖

主要依赖的

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
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springframework.version>4.1.6.RELEASE</springframework.version>
<springsecurity.version>4.0.1.RELEASE</springsecurity.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${springsecurity.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${springsecurity.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<finalName>sprsec</finalName>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.7.v20170914</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>

java文件

Servlet初始化器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SpringMvcInitializer  extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { HelloWorldConfiguration.class };
}

@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}

@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}

}

这里的HelloWorldConfiguration就是SpringMVC的配置初始化类。
AbstractAnnotationConfigDispatcherServletInitializer是继承于WebApplicationInitializer可能在web容器启动的时候被加载。

SpringMVC 配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.sprsec")
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {

@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
}

这个类的作用相当于把原来xml的配置变成了java代码中写的。
等价于下面的xml:

1
2
3
4
5
6
7
8
9
10
11
12
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<context:component-scan base-package="com.example.sprsec" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/js/**" location="/js/" />

初始化 Spring Security 的类

1
2
3
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

}

这个类的作类,类似在web.xml中增加Filter。由于这个项目是servlet3.1,没有web.xml,都是用java类来配置的。
详细介绍见Spring Security 4官方文档中文翻译与源码解读
原理就是用了spring-web-4.xx.xx.jar的中SPI机制。
等同于下面配置:

1
2
3
4
5
6
7
8
9
<filter>  
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Spring Security 配置类

该类主要用来配置权限与url的对应关系,及验证方式

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

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
CustomSuccessHandler customSuccessHandler;

@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("123123").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("123123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("dba").password("123123").roles("ADMIN", "DBA");

}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").access("hasRole('USER')")
.antMatchers("/admin/**").access("hasRole('ADMIN')")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin().loginPage("/login")
.successHandler(customSuccessHandler)
.usernameParameter("ssoId")
.passwordParameter("password")
.and().csrf().and().exceptionHandling()
.accessDeniedPage("/Access_Denied");
}

}

等价于下面的xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/home/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/db/**" access="hasRole('ADMIN') and hasRole('DBA')" />
<intercept-url pattern="/**" access="hasRole('USER')" />
<access-denied-handler error-page="/Access_Denied" />
<form-login
login-page="/login"
default-target-url="/index"
authentication-failure-url="/login?error"
username-parameter="ssoId"
password-parameter="password" />
<csrf/>
</http>

验证后的处理类

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

@Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

@Override
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
System.out.println("Can't redirect");
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}

private String determineTargetUrl(Authentication authentication) {
String url = "";
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
List<String> roles = new ArrayList<String>();
for (GrantedAuthority a : authorities) {
roles.add(a.getAuthority());
}
if (roles.contains("ROLE_DBA")) {
url = "/db";
} else if (roles.contains("ROLE_ADMIN")) {
url = "/admin";
} else if (roles.contains("ROLE_USER")) {
url = "/home";
} else {
url = "/accessDenied";
}
return url;
}

}

控制器

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

@Controller
public class MainController {

@RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET)
public String homePage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "welcome";
}

@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "admin";
}

@RequestMapping(value = "/db", method = RequestMethod.GET)
public String dbaPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "dba";
}

@RequestMapping(value = "/Access_Denied", method = RequestMethod.GET)
public String accessDeniedPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "accessDenied";
}

@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage() {
return "login";
}

@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login?logout";
}

private String getPrincipal() {
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
return userName;
}
}

jsp文件

  • login.jsp 登录页面,有登录的form表单
  • index.jsp 普通用户的欢迎页面
  • admin.jsp 管理员的欢迎页面
  • dba.jsp dab的欢迎页面
  • welcome.jsp 普通用户的欢迎页面
  • accessDenied.jsp 验证失败的页面

参考