1. 基本概要
Spring Security基于Spring框架,提供了一套Web应用安全性的完整解决方案.
安全方面的两个主要区域是"认证"和"授权"(或者访问控制),一般来说,Web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分
- 用户认证是指:验证某个用户是否位系统中的合法主体,也就是说用户能否访问该系统.用户认证一般要求用户提供用户名和密码.系统通过校验用户名和密码来完成认证过程.简单来讲就是系统认为用户是否能登录
- 用户授权是指:验证某个用户是否有权限执行某个操作.在一个系统中,不同用户所具有的权限是不同的.比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改.一般来说,系统会为不同的用户分配不同的角色.而每个角色则对应一系列的权限.简单来讲,就是系统判断用户是否具有权限去做某些事情
本质上是一个过滤器链.
2. 基本使用
2.1 Security认证
- 通过配置文件
spring.security.user.name=ADMIN
spring.security.user.password=ADMIN
-
通过配置类
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("ADMIN") .password(passwordEncoder().encode("123456")) .roles("admin"); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
-
自定义编写实现类
-
配置类
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
-
UserDetails实现类
@Service("userDetailsService") public class UserService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info("userName:{}", StringEscapeUtils.escapeJava(userName)); if (StringUtil.isEmpty(userName)) { throw new BusinessException("用户名不可为空"); } // 从数据库读取用户账户 UserAccount user = userMapper.findByUserName(userName); if (null == user) { throw new BusinessException("用户信息不存在"); } return new org.springframework.security.core.userdetails.User(userName, passwordEncoder.encode(user.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
-
2.2 自定义登录界面
-
在
WebSecurityConfig
配置类中实现相关的配置private static final String[] requests = new String[]{"/**/*.js", "/**/*.html","/**/*.css", "/oauth/**", "/**/*.jpg","/**/*.png","/**/*.ttf", "/**/*.woff","/**/*.woff2", "/user/login"}; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 自定义登录页面 .loginPage("/login.html") // 登录页面设置 .loginProcessingUrl("/user/login") // 登录访问路劲 .defaultSuccessUrl("/user/loginSuccess")// 登录成功之后跳转的路径 .permitAll() .and().authorizeRequests() // 定义那些路径被保护,哪些不被保护 .antMatchers(requests).permitAll() // 设置哪些路接可以直接访问,不需要认证 .anyRequest().authenticated() // 其他请求都必须有权限认证 .and().csrf().disable(); // 暂时禁用CSRF,否则无法提交登录表单 }
-
创建相关页面
<form action = "/user/login" method = "POST"> 用户名:<input type = "text" name = "username" /><br/> 密码:<input type = "password" name = "password" /><br/> <input type = "sibmit" value = "登录"/> </form>
输入变量为username和password,
UsernamePasswordAuthenticationFilter
过滤器会从post请求中获取这两个参数
2.3 基于角色或权限进行访问控制
2.3.1 hasAuthority
如果当前主体具有指定的权限,则返回true,否则返回false
-
在配置类中设置当前访问地址需要有哪些权限
// 当前登录用户,只有同时具有admin和manager权限才可以访问这个路径 .antMatchers("/test/index").hasAuthority("admin,manager")
-
在
UserDetailService
中把返回的User对象设置权限return new org.springframework.security.core.userdetails.User(userName, passwordEncoder.encode(user.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
2.3.2 hasAnyAuthority
如果当前主体有任何提供的角色(给定的部分作为一个逗号分隔的字符串列表)的话,返回true
-
在配置类中设置当前访问地址需要有哪些权限
// 当前登录用户,只要具有admin和manager其中一个权限就可以访问这个路径 .antMatchers("/test/index").hasAnyAuthority("admin,manager")
-
在
UserDetailService
中把返回的User对象设置权限return new org.springframework.security.core.userdetails.User(userName, passwordEncoder.encode(user.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
2.3.3 hasRole
如果用户具备给定角色就允许访问,返回true,否则返回403
-
在配置类中设置当前访问地址需要有哪些权限
// 当前登录用户,只要具有admin和manager其中一个权限就可以访问这个路径 .antMatchers("/test/index").hasRole("salesperson")
-
在
UserDetailService
中把返回的User对象设置权限return new org.springframework.security.core.userdetails.User(userName, passwordEncoder.encode(user.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_salesperson"));
2.3.4 hasAnyRole
-
在配置类中设置当前访问地址需要有哪些权限
// 当前登录用户,只要具有admin和manager其中一个权限就可以访问这个路径 .antMatchers("/test/index").hasRole("salesperson")
-
在
UserDetailService
中把返回的User对象设置权限return new org.springframework.security.core.userdetails.User(userName, passwordEncoder.encode(user.getPassword()), AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_salesperson"));
2.3.5 自定义403页面
在配置类中进行配置
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedPage("/forbidden.html");
}
2.4 注解
2.4.1 @Secured
判断用户是否具有角色(匹配的字符串需要添加前缀"ROLE_")
使用注解前需要开启注解功能
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled=true)
public class MainApplication {
}
在控制器方法上添加注解
@RequestMapping("")
@ResponseBody
@Secured({"ROLE_ADMIN","ROLE_USER"})
public String login() {
return "";
}
2.4.2 @PreAuthorize
@PreAuthorize
适合进入方法前的权限验证,可以将登录用户的roles/permissions
参数传入到方法中
使用注解前需要开启注解功能
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MainApplication {
}
在控制器方法上添加注解
@RequestMapping("")
@ResponseBody
@PreAuthorize("hasAnyAuthority('admin')")
public String login() {
return "";
}
2.4.3 @PostAuthorize
@PostAuthorize
执行方法后的权限验证
使用注解前需要开启注解功能
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MainApplication {
}
在控制器方法上添加注解
@RequestMapping("")
@ResponseBody
@PostAuthorize("hasAnyAuthority('admin')")
public String login() {
return "";
}
2.4.4 @PreFilter
@PreFilter
:进入控制器前对数据进行过滤
@RequestMapping("")
@ResponseBody
@PreFilter(value = "filterObject.id % 2 == 0")
public String login(List<User> userList) {
return "";
}
2.4.5 @PostFilter
2.5 用户注销
在配置类中添加退出登录映射地址
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
2.6 实现记住我(免登录)
2.6.1 Cookie
2.6.2 数据库 Remember Me
-
创建数据库表
CREATE TABLE `persistent_logins` IF NOT EXISTS { `username` VARCHAR(64) NOT NULL, `series` VARCHAR(64) NOT NULL, `token` VARCHAR(64) NOT NULL, `last_userd` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) } ENGINE=INNODB DEFAULT CHARSET=utf8;
-
修改配置类(修改数据源)
@Autowired private DataSource dataSource; @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; }
-
配置类中配置自动登录
@Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling().accessDeniedPage("/forbidden.html"); http.formLogin() // 自定义登录页面 .loginPage("/login.html") // 登录页面设置 .loginProcessingUrl("/user/login") // 登录访问路劲 .defaultSuccessUrl("") // 登录成功之后跳转的路径 .permitAll() .and().authorizeRequests() // 定义那些路径被保护,哪些不被保护 .antMatchers(requests).permitAll() // 设置哪些路接可以直接访问,不需要认证 .anyRequest().authenticated() // 其他请求都必须有权限认证 .and().rememberMe() // 设置remember me .tokenRepository(persistentTokenRepository()) // 设置数据库 .tokenValiditySeconds(60) // 设置有效期60s, .userDetailsService(userDetailsService) // 操作数据库的实现类 .and().csrf().disable(); // 暂时禁用CSRF,否则无法提交登录表单 http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll(); }
-
登录页面设置remember me的复选框
<input type = "checkbox" name = "remember-me"/> 记住我
复选框名字需要为remember-me