Spring Security是Spring家族中历史比较悠久的框架之一,具备完整而强大的功能体系。对于日常开发过程中常见的单体应用、微服务架构,以及响应式系统,Spring Security都能够进行无缝集成和整合,并提供多种常见的安全性功能。其核心主要包括:用户信息管理、敏感信息加解密、用户认证、权限控制、跨站点请求伪造保护、跨域支持、全局安全方法、单点登录等。
随着Spring Boot的兴起,开发人员可以零配置使用Spring Security。在Spring Boot应用程序中使用Spring Security,只须在Maven工程的pom文件中添加如下依赖即可。
org.springframework.boot
spring-boot-starter-security
在添加依赖后,spring security会自动为URL增加一个登录拦截。默认用户名是user,密码在springboot的启动日志里可以找到。
Spring Security中,安全配置通过继承WebSecurityConfigurerAdapter
来配置。
@Configuration
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter{
protected void configure(HttpSecurity http) throws Exception {
//做大量的配置
//认证配置
//授权配置
}
认证 authentication ,解决你是谁的问题。
public interface UserMapper {
User selectByUserName(String userName);
List<String> selectAllRoleByUserId(Integer userId);
List<String> selectPermissionsByUserId(Integer userId);
}
B、配置userMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.mapper.UserMapper">
<select id="selectByUserName" resultType="com.demo.entity.User">
select id,userName,password
from t_user where username = #{userName}
select>
<select id="selectAllRoleByUserId" resultType="String">
select r.name as name from t_role_user u,t_role r where r.id = u.rid and u.uid=#{userId};
select>
<select id="selectPermissionsByUserId" resultType="String">
SELECT permission FROM t_role r,t_role_user u,t_role_menu rm,t_menu m
where r.id = u.uid and rm.mid = m.id and u.rid =rm.rid and u.uid=#{userId}
select>
mapper>
C、新建一个类实现org.springframework.security.core.userdetails.UserDetailsService
接口,这样就会调用该类的方法去访问数据库认证了。
这一步中会返回一个User对象,该对象是SpringSecurity里定义的User对象。
需要将用户的权限和角色用逗号分割后组装在一起。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
com.demo.entity.User user = userMapper.selectByUserName(s);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
List<String> roles = userMapper.selectAllRoleByUserId(user.getId());
List<String> permissions = userMapper.selectPermissionsByUserId(user.getId());
StringBuilder sb = new StringBuilder();
for (String role : roles) {
sb.append("ROLE_" + role + ",");
}
for (String permission : permissions) {
sb.append(permission + ",");
}
String rolepermission = sb.substring(0, sb.length() - 1);
UserDetails userDetails = new User(user.getUserName(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(rolepermission));
return userDetails;
}
}
D、注意,在上步骤中需要使用到一个passwordEncoder编码器,来生成加密密码明文生成密文。
@Autowired
private PasswordEncoder passwordEncoder;
通过java类来配置一个passwordEncoder
@Configuration
public class SecurityConfig {
@Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
MyWebSecurityConfigurerAdapter
中配置/showLogin
、/login
、/showMain
、/showFail
。.usernameParameter("myusername")
以及.passwordParameter("mypassword")
定义了登录页面中的表单名称是myusername
和mypassword
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter{
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
//未登录时的地址
.loginPage("/showLogin")
//处理登录请求的url
.loginProcessingUrl("/login")
//.successForwardUrl("/showMain")
//认证成功后跳转的地址
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect("/showMain");
}
})
//.failureForwardUrl("/showFail")
//登录失败后的处理器
.usernameParameter("myusername")
//客户端的密码参数名称
.passwordParameter("mypassword")
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendRedirect("/showFail");
}
})
;
}
}
配置异常页面,如果程序异常测跳转到该页面。
http.exceptionHandling()
//.accessDeniedHandler(accessDeniedHandler);
//只适用于非前端框架,适用于同步请求的方式
//如果是异步请求需要使用上一种方式。
.accessDeniedPage("/showAccessDenied");
如果是异步请求,如ajax,则可以实现AccessDeniedHandler
接口
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(httpServletResponse.SC_FORBIDDEN);
PrintWriter writer = httpServletResponse.getWriter();
writer.println("权限不足");
writer.flush();
writer.close();
}
}
需要配置生成一个token,并将token下发给浏览器。
因此需要一个PersistentTokenRepository
类。
通过java类的方式来定义bean,这个bean就是persistentTokenRepository
@Configuration
public class RememberMeConfig {
@Autowired
private DataSource dataSource;
@Bean
protected PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
//jdbcTokenRepository.setCreateTableOnStartup(true);
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
}
在MyWebSecurityConfigurerAdapter
中配置.rememberMe()
private MyAccessDeniedHandler accessDeniedHandler;
private PersistentTokenRepository persistentTokenRepository;
protected void configure(HttpSecurity http) throws Exception {
//其他配置
http.rememberMe()
.userDetailsService(userDetailsService)
.tokenRepository(persistentTokenRepository)
.tokenValiditySeconds(10*2);
//其他配置
}
授权 authorization,解决你能做什么的问题。授权一般需要依托用户的角色,对权限集合进行绑定。
antMatchers("/showLogin","/showFail").access("permitAll")
也直接放行。.antMatchers("/images/**").permitAll() .regexMatchers("/js/.*").permitAll() .antMatchers("/demo").permitAll()
这几个直接放行。 .antMatchers("/abc").denyAll()
所有到/abc的请求都拒绝。.anyRequest().authenticated();
声明所有的请求都需要登录。public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter{
protected void configure(HttpSecurity http) throws Exception {
//前面的认证配置
http.authorizeRequests()
//.antMatchers("/showLogin","/showFail").permitAll()
.antMatchers("/showLogin","/showFail").access("permitAll")
//对于静态和动态请求需要分开
//.antMatchers("/js/**").permitAll()
.antMatchers("/abc").denyAll()
.antMatchers("/jczl").hasAnyAuthority("demo:update")
//.antMatchers("/jczl").hasAnyRole("ADMIN")
.antMatchers("/admin").access("@myServiceImpl.hasPermission(request,authentication)")
.antMatchers("/ip").hasIpAddress("192.168.7.86")
.antMatchers("/images/**").permitAll()
.regexMatchers("/js/.*").permitAll()
.antMatchers("/demo").permitAll()
.anyRequest().authenticated();
为防止跨越的表单提交,在login.html 的隐藏域中需要加上
然后在MyWebSecurityConfigurerAdapter
中继续取消 http.csrf().disable();
的注释,即默认开启csrf的校验了。
整体感受springsecurity的应用主要是要熟悉,并且跟随一起动手。springsecurity在微服务、OAuth2、响应式编程中都有大量的应用。在这里就不谈那么深了。
项目的的github地址是:https://github.com/forestnlp/springsecurity