Spring Security是Spring提供登录认证,鉴权的组件,使用起来很方便,所以越来越多的系统使用Spring Security完成登录校验功能,Spring Security使用Filter过滤器检查客户端连接是否完成登录校验,使用Cookie记录登录状态,下面例程演示Spring Securiy登录和授权。
1、创建Maven工程,引入Spring Securiy依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
2、创建配置文件,/src/main/resources/application.yml
server:
port: 6060
3、编写启动类
package com.hk.sec;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StartApp {
public static void main(String[] args)
{
SpringApplication.run(StartApp.class, args);
}
}
4、测试Spring Security
对Spring Security没有做更多配置,默认登录用户是user,密码在启动控制台上有显示
通过浏览器登录系统http://localhost:6060
登录成功后出现
5、以上是最简化地使用了Spring Security,在实际项目中需要对登录和授权做更细致的控制,比如用户和密码放在DB,LDAP或者第三方平台登录(qq,微信),授权控制需要通过角色来控制权限。
6、集成数据库用户和密码校验,实现接口UserDetailsService,接口UserDetailsService是Spring Security中定义的接口,Spring Security使用UserDetailsService获取用户对象
package com.hk.sec;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
public class UserService implements UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("loadUserByUsername username==="+username);
if("user".equals(username))
{
UserInfo u = new UserInfo("user","123");
u.addRole("ROLE_ORG");
u.addRole("ROLE_USER");
return u;
}
return null;
}
}
在类UserService 可以连接数据库将用户名和密码加载出来。
7、配置如何使用自定义类UserService获取用户数据
定义WebSecurityConfigurerAdapter子类,并实现configure方式,通过 定义类UserService获取用户信息
@Configuration
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserDetailsService uds;
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds);
//auth.inMemoryAuthentication()
//.withUser("orguser").password("123").roles("ORG","USER")
//.and()
//.withUser("user").password("123").roles("USER")
//.and()
//.withUser("roleuser").password("123").roles("ROLE");
}
}
这样就可以使用数据库中的用户和密码进行系统校验。
8、如何使用角色控制资源的访问
编写三个Controller,分别对应三个访问权限
@RestController
public class OrgCtrl {
@GetMapping("/org")
public String hello() {
return "Org";
}
}
@RestController
public class RoleCtrl {
@GetMapping("/role")
public String hello() {
return "Role";
}
}
@RestController
public class UserCtrl {
@GetMapping("/user")
public String hello() {
return "User";
}
}
9、配置WebSecurityConfigurerAdapter子类中的configure方法
package com.hk.sec;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserDetailsService uds;
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds);
//auth.inMemoryAuthentication()
//.withUser("orguser").password("123").roles("ORG","USER")
//.and()
//.withUser("user").password("123").roles("USER")
//.and()
//.withUser("roleuser").password("123").roles("ROLE");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/org/**")
.hasRole("ORG")// /org资源必须具有ORG角色
.antMatchers("/user/**")
.hasRole("USER")// /user资源必须具有USER角色
.antMatchers("/orguser/**")
.access("hasRole('ORG') and hasRole('USER')") // /orguser资源必须具有USER角色并且具有ORG角色
.antMatchers("/role/**")
.access("hasAnyRole('ORG') and hasRole('ROLE')")
.anyRequest()
.authenticated()
.and()
.csrf()
.disable();
}
}
10、自定义登录页面
package com.hk.sec;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserDetailsService uds;
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds);
//auth.inMemoryAuthentication()
//.withUser("orguser").password("123").roles("ORG","USER")
//.and()
//.withUser("user").password("123").roles("USER")
//.and()
//.withUser("roleuser").password("123").roles("ROLE");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/org/**")
.hasRole("ORG")
.antMatchers("/user/**")
.hasRole("USER")
.antMatchers("/orguser/**")
.access("hasRole('ORG') and hasRole('USER')")
.antMatchers("/role/**")
.access("hasAnyRole('ORG') and hasRole('ROLE')")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/loginpage") //自定义登录页面
.loginProcessingUrl("/logindone")
.permitAll()
.and()
.csrf()
.disable();
}
}
11、对前后端分离的登录认证支持,前后端分离登录返回JSON对象,不能跳转页面,所以需要定义返回信息
package com.hk.sec;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserDetailsService uds;
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds);
//auth.inMemoryAuthentication()
//.withUser("orguser").password("123").roles("ORG","USER")
//.and()
//.withUser("user").password("123").roles("USER")
//.and()
//.withUser("roleuser").password("123").roles("ROLE");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/org/**")
.hasRole("ORG")
.antMatchers("/user/**")
.hasRole("USER")
.antMatchers("/orguser/**")
.access("hasRole('ORG') and hasRole('USER')")
.antMatchers("/role/**")
.access("hasAnyRole('ORG') and hasRole('ROLE')")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/logindone")
.successHandler(new AuthenticationSuccessHandler() { //登陆成功后
@Override
public void onAuthenticationSuccess(HttpServletRequest req,
HttpServletResponse resp,
Authentication auth) //当前用户登陆信息
throws IOException {
Object principal = auth.getPrincipal();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
resp.setStatus(200);
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("msg", principal);
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() { //登陆失败后
@Override
public void onAuthenticationFailure(HttpServletRequest req,
HttpServletResponse resp,
AuthenticationException e) //获取登陆失败原因
throws IOException {
e.printStackTrace();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
resp.setStatus(401);
Map<String, Object> map = new HashMap<>();
map.put("status", 401);
if (e instanceof LockedException) {
map.put("msg", "账户被锁定,登录失败!");
} else if (e instanceof BadCredentialsException) {
map.put("msg", "账户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
map.put("msg", "账户被禁用,登录失败!");
} else if (e instanceof AccountExpiredException) {
map.put("msg", "账户已过期,登录失败!");
} else if (e instanceof CredentialsExpiredException) {
map.put("msg", "密码已过期,登录失败!");
} else {
map.put("msg", "登录失败!");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll()
.and()
.logout()//开启注销登陆
.logoutUrl("/logout")//注销登陆请求url
.clearAuthentication(true)//清除身份信息
.invalidateHttpSession(true)//session失效
.addLogoutHandler(new LogoutHandler() {//注销处理
@Override
public void logout(HttpServletRequest req,
HttpServletResponse resp,
Authentication auth) {
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() { //注销成功处理
@Override
public void onLogoutSuccess(HttpServletRequest req,
HttpServletResponse resp,
Authentication auth)
throws IOException {
resp.sendRedirect("/login_page"); //跳转到自定义登陆页面
}
})
.and()
.csrf()
.disable();
}
}
12、对应用户密码错误、用户失效、用户被锁等场景,Spring Secruity都提供解决方案
.failureHandler(new AuthenticationFailureHandler() { //登陆失败后
@Override
public void onAuthenticationFailure(HttpServletRequest req,
HttpServletResponse resp,
AuthenticationException e) //获取登陆失败原因
throws IOException {
e.printStackTrace();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
resp.setStatus(401);
Map<String, Object> map = new HashMap<>();
map.put("status", 401);
if (e instanceof LockedException) {
map.put("msg", "账户被锁定,登录失败!");
} else if (e instanceof BadCredentialsException) {
map.put("msg", "账户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
map.put("msg", "账户被禁用,登录失败!");
} else if (e instanceof AccountExpiredException) {
map.put("msg", "账户已过期,登录失败!");
} else if (e instanceof CredentialsExpiredException) {
map.put("msg", "密码已过期,登录失败!");
} else {
map.put("msg", "登录失败!");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
代码下载
链接:https://pan.baidu.com/s/1b4uzATldm5CeuzHrtXsnQA
提取码:3i8f