本文将具体介绍在Spring Boot中如何使用Spring Security进行安全控制,权限控制数据均有数据库查询。
1.背景
Spring Security 主要是在访问前添加过滤器,过滤器中主要起作用的为 访问鉴权authenticationManager(有没有权限访问系统) 和 访问决策器accessDecisionManager(可以访问系统的哪些资源,当时此处涉及查询数据库资源,还需要数据资源查询securityMetadataSource),具体的对应springmvc 中的配置地址为:https://blog.51cto.com/5148737/1615882,本文将基于上文的改造,改造成springboot版本。
2.添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 如果需要jsp支持security标签,需要添加这个 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
到此为止,项目路径如下
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gosun</groupId>
<artifactId>cdn</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>
HomeController.java
@Controller
public class HomeController {
@RequestMapping("/")
public void index(HttpServletRequest request,HttpServletResponse response) throws IOException {
response.getWriter().write("index");
}
}
这个时候如果项目跑起来的话,springsecurity会使用内部默认控制,控制台会打印密码,用户名为user,如下
但是我们要讲的是,自定义权限控制,ok,下面继续
3.构建securiy配置文件WebSecurityConfig,需要继承WebSecurityConfigurerAdapter
在这个类中,加入了自定义访问鉴权,访问决策器,资源池查询
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService userDetailService;
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setPasswordEncoder(passwordEncoder());
authProvider.setUserDetailsService(userDetailService);
ReflectionSaltSource saltSource = new ReflectionSaltSource();
saltSource.setUserPropertyToUse("username");
authProvider.setSaltSource(saltSource);
auth.authenticationProvider(authProvider);
}
@Bean
public Md5PasswordEncoder passwordEncoder() {
return new Md5PasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/assets/**", "/portal/**", "/login","/redirect","/login/**").permitAll()
.antMatchers("/schedual/area_isp_ip", "/403", "/404.jsp", "/logout", "/favicon.ico","/favicon.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(authenticationSuccessHandler).defaultSuccessUrl("/").permitAll()
.failureUrl("/login?error").permitAll()
.and()
.logout()
.permitAll()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi) {
fsi.setSecurityMetadataSource(mySecurityMetadataSource());
fsi.setAccessDecisionManager(myAccessDecisionManager());
return fsi;
}
});
}
@Bean
public FilterInvocationSecurityMetadataSource mySecurityMetadataSource() {
MyInvocationSecurityMetadataSourceService securityMetadataSource = new MyInvocationSecurityMetadataSourceService();
return securityMetadataSource;
}
@Bean
public AccessDecisionManager myAccessDecisionManager() {
return new MyAccessDecisionManager();
}
}
相关说明:
- 该类需要继承WebSecurityConfigurerAdapter,重写configure方法,注解
@Configuration:为配置文件,自动加载;
@EnableWebSecurity:表示开启security权限控制 - configure(HttpSecurity http)
http.csrf().disable() 禁用了csrf,支持jsp时需要加此条件
http.authorizeRequests().xxxxx 开始添加相关条件
antMatchers.....为需要过滤的url,即这些地址url都不需要进行权校验
.formLogin().loginPage("/login") 定义了默认的登陆地址
.successHandler.....定义了登陆成功后的处理方法,主要将用户信息放入session等
.failureUrl.....定义了失败的url
.withObjectPostProcessor......则定义了访问决策器、资源池查询 两个方法
springmvc中定义filter,将访问决策器、访问决策器、资源池查询定义在filter中,在网上一些案例中,看到其他文章springboot 整合springsecurity的时候,采用添加filter的方式,但是亲自试验后发现有问题,antMatchers会失效,如果所有的url都需要进行权限校验,那么可以采用添加filter的形式,但是如果向上述,需要部分url比如说静态文件直接可以访问,那么必须采用这种withObjectPostProcessor的方式。 - configure(AuthenticationManagerBuilder auth) 定义了访问鉴权
其中鉴权最重要的是需要 authProvider,
setPasswordEncoder :定义何种密码校验方式Md5PasswordEncoder
setUserDetailsService :自定义了数据库查询用户
setSaltSource :密码添加盐值
4.资源池查询类(MyInvocationSecurityMetadataSourceService)
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
private AntPathMatcher urlMatcher = new AntPathMatcher();
private static Map <String,Collection <ConfigAttribute>> resourceMap = null;
@Autowired
CommonDao commonDao;
/**
* 加载URL权限配置
*/
private void loadResourceDefine() {
long starttime = new Date().getTime();
String sql = "SELECT * FROM auth_resource";
resourceMap = new HashMap<String,Collection <ConfigAttribute >> ();
List<Properties> resourceList = commonDao.queryForList(sql);
List<Map> roleList = commonDao.queryForList("SELECT * FROM auth_role");
Map<String,String> roleIdMap = new HashMap();
for(Map roleMap:roleList){
String roleId = roleMap.get("id").toString();
String rolename = roleMap.get("rolename").toString();
roleIdMap.put(roleId, rolename);
}
long endtime = new Date().getTime();
System.out.println("查询完毕,耗时"+(endtime-starttime)+"ms");
for(Map dataMap:resourceList){
String urlPattern ="";
if(dataMap.get("url_pattern")!=null){
urlPattern = dataMap.get("url_pattern").toString();
}
String[] roleIds = new String[0];
if(dataMap.get("access_role")!=null&&!dataMap.get("access_role").equals("")){
String acce***ole = dataMap.get("access_role").toString();
roleIds = acce***ole.split(",");
}
Collection <ConfigAttribute> atts = new ArrayList < ConfigAttribute >();
for(String roleId:roleIds){
ConfigAttribute ca = new SecurityConfig(roleIdMap.get(roleId));
atts.add(ca);
}
resourceMap.put(urlPattern,atts);
}
endtime = new Date().getTime();
System.out.println("加载系统权限配置完毕,耗时"+(endtime-starttime)+"ms");
}
public boolean isResourceMapEmpty(){
if(resourceMap==null){
return true;
}else{
return false;
}
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
}
public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
if(resourceMap==null) {
loadResourceDefine();
}
// TODO Auto-generated method stub
String url =((FilterInvocation)object).getRequestUrl();
if(resourceMap != null){
Set<String> urlPatternSet = resourceMap.keySet();
for(String urlPattern:urlPatternSet){
if(urlMatcher.match(urlPattern, url)){
return resourceMap.get(urlPattern);
}
}
}
return null;
}
public boolean supports(Class<?> arg0) {
// TODO Auto-generated method stub
return true;
}
public String resourceSelfMatcher(String resURL){
return null;
}
/**
* 刷新资源配置
*/
public void refreshResource(){
loadResourceDefine();
}
}
MyAccessDecisionManager)
public class MyAccessDecisionManager implements AccessDecisionManager {
@Autowired
MyInvocationSecurityMetadataSourceService securityMetadataSource;
public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,InsufficientAuthenticationException {
// TODO Auto-generated method stub
// if(securityMetadataSource.isResourceMapEmpty()){
// securityMetadataSource.refreshResource();
// }
if (configAttributes == null ) throw new AccessDeniedException("对不起,您没有此权限");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date())+":\t"+object.toString());
for(ConfigAttribute ca:configAttributes){
String needRole = ca.getAttribute();
for(GrantedAuthority userGA:authentication.getAuthorities()) {
if(needRole.equals(userGA.getAuthority())) { // ga is user's role.
return ;
}
}
}
throw new AccessDeniedException("对不起,您没有此权限");
}
public boolean supports(ConfigAttribute arg0) {
// TODO Auto-generated method stub
return true;
}
public boolean supports(Class<?> arg0) {
// TODO Auto-generated method stub
return true;
}
}
6.自定义用户查询(MyUserDetailService)
@Service("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
CommonDao commonDao;
String userTable = "auth_user";
String roleTable = "auth_role";
String menuTable = "auth_resource";
public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException, DataAccessException {
Map userMap= commonDao.queryForOne("SELECT * FROM `"+userTable+"` WHERE username='"+username+"'");
if(userMap!=null&userMap.containsKey("username")){
//初始化角色信息
Collection <GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
String[] userRoles = userMap.get("user_role").toString().split(",");
for(String userRole:userRoles){
Map roleMap = commonDao.queryForOne("SELECT * FROM `"+roleTable+"` WHERE id="+ userRole);
if(roleMap!=null && roleMap.containsKey("rolename")){
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(roleMap.get("rolename").toString());
authorities.add(authority);
}
}
boolean enabled = false;
String nickname = username;
String telephone = "";
String email = "";
int sex = 0;
String password = "";
if(userMap.get("telephone")!=null) telephone = userMap.get("telephone").toString();
if(userMap.get("password")!=null) password = userMap.get("password").toString();
if(userMap.get("nickname")!=null) nickname = userMap.get("nickname").toString();
if(userMap.get("email")!=null) email = userMap.get("email").toString();
if(userMap.get("sex")!=null) sex = Integer.parseInt(userMap.get("sex").toString());
if(Integer.parseInt(userMap.get("enabled").toString())==1) enabled = true;
User userdtails = new User(username, password, enabled, true, true,true, authorities,null,nickname,telephone,email,sex);
return userdtails;
}else{
return null;
}
}
}
7.权限校验成功返回handler AuthenticationSuccessHandler)
@Service
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private CommonDao commonDao;
private RequestCache requestCache;
public AuthenticationSuccessHandler(){
this.requestCache = new HttpSessionRequestCache();
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication)throws ServletException, IOException {
User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
request.getSession().setAttribute("userDetails", userDetails);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
}