一、SpringSecurity-简介
- 概念
- Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于spring的应用程序的实际标准。
- Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它很容易扩展以满足定制需求
- 4种使用方式
- ①全部利用配置文件,将用户、权限、资源(url)硬编码在xml文件中
- ②用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置
- ③细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器, 并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。
- ④修改springsecurity的源代码,主要是修改InvocationSecurityMetadataSourceService和UserDetailsService两个类。
- InvocationSecurityMetadataSourceService
- 将配置文件或数据库中存储的资源(url)提取出来加工成为url和权限列表的Map供Security使用
- UserDetailsService
- 提取用户名和权限组成一个完整的(UserDetails)User对象,该对象可以提供用户的详细信息供AuthentationManager进行认证与授权使用
我采用的数据库方式储存权限
二、SpringSecurity-HelloWorld
- 1、引入SpringSecurity的依赖,pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<!-- 标签库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
- 2、 编写SpringSecurity的配置
- web.xml配置Security的Filter拦截所有请求
<!-- 权限过滤Filter -->
<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>
- 3、控制登录
- 编写SpringSecurity的配置类
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
@Configuration
@EnableWebSecurity
public class AtcrowdfundingSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
// 认证和授权
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//new BCryptPasswordEncoder()是对密码的加密算法
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 设置哪些资源可以被访问
http.authorizeRequests()
//这些资源可以直接访问
.antMatchers("/static/**", "/welcome.jsp", "/login").permitAll()
//其他的访问需要认证
.anyRequest().authenticated();
//设置没有登录不能访问的资源跳转到我们自己的登录页(他存在一个默认的很丑的登录页)
http.formLogin()
.loginPage("/login")//请求到我们自己写的登录界面
.usernameParameter("loginacct")//这里填入我们登陆界面的用户名name属性
.passwordParameter("userpswd")//密码
.loginProcessingUrl("/doLogin")//登录信息验证请求
.defaultSuccessUrl("/main");//登录成功后跳转的界面
//禁用跨站请求伪造,让测试更简单,实际情况通常不会禁用
http.csrf().disable();
// 对无权访问的处理
http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
String header = request.getHeader("X-Requested-With");
// 如果是异步请求
if ("XMLHttpRequest".equals(header)) {
response.getWriter().print("403");
} else {
request.getRequestDispatcher("/WEB-INF/jsp/error/error403.jsp").forward(request, response);
;
}
}
});
// 退出登录后跳转的请求
http.logout().logoutSuccessUrl("/index");
http.rememberMe();
}
}
- 验证登录信息是否正确,如果正确,查出他拥有的所有权限
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private TAdminMapper adminMapper;
@Autowired
private TRoleMapper roleMapper;
@Autowired
private TPermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 通过用户名认证
TAdminExample example = new TAdminExample();
example.createCriteria().andUsernameEqualTo(username);
List<TAdmin> admins = adminMapper.selectByExample(example);
TAdmin admin = null;
if (admins != null && admins.size() != 0) {
admin = admins.get(0);
}
// 认证结束
// 查询用户角色
int adminId = admin.getId();
List<TRole> roles = roleMapper.queryRoleByAdminId(adminId);
// 查询用户拥有的权限
List<TPermission> permissions = permissionMapper.queryPermissionByAdminId(adminId);
// 用户拥有的权限(角色+权限)
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (TRole role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
for (TPermission permission : permissions) {
authorities.add(new SimpleGrantedAuthority(permission.getName()));
}
return new User(admin.getLoginacct(), admin.getUserpswd(), authorities);
}
}
- 4、细粒度权限功能
- 加入注解到SpringSecurity的配置类,即上面的AtcrowdfundingSecurityConfig类
//开启细粒度权限功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
- 可以精确到一个按钮的权限
- ①是组长身份才能看到“管理组员”按钮
<sec:authorize access="hasRole('TL - 组长')">
<button type="button" class="btn btn-default btn-danger">
<span class="glyphicon glyphicon-question-sign">管理组员</span>
</button>
</sec:authorize>
- ②不隐藏按钮,而是在点击后告诉用户没有权限
- 我们将权限控制加到方法上面
- 如果没有权限会跳转的我们最初配置的SpringSecurity的配置类里面的拒绝访问
// 用户新增
@PreAuthorize("hasAuthority('user:add')")
@RequestMapping("/admin/doAdd")
public String addAdmin(TAdmin admin) {
adminService.saveAdmin(admin);
return "redirect:/admin/index?pageNum=" + Integer.MAX_VALUE;
}