Spring Security

安全及权限控制是每个应用难以绕开的部分,这部分业务也不是特别复杂,但是我想这块业务具有通用性的特点,既然有开源的框架,完全可以拿来主义,没有必要重复造轮子。虽然学习新的框架前期需要投入一些精力,但开源框架的架构设计会比还是菜鸟的我设计出来的好点,而且会用之后,后期的收益还是不错的。在Java Web开发中,安全框架Spring Security和Apache Shiro比较主流,虽然Shiro入门简单些,但是由于个人非常喜欢Spring就选择Spring Security。

理论上,Spring Security和SpringMVC结合使用,会简单的多,而且很多资料的也是根据SpringMVC的介绍的,但是部门使用的项目的Struts2+Spring4.2+Hibernate4.3.。那我只好在此基础上集成Spring Security,这会稍微麻烦一点,像我这样的小白独立研究起来,真是跌跌撞撞哎。

废话有些多,项目框架上面说完了。我采用的Spring Security的版本为4.0.3.集成步骤如下:

1.通过Java编程的方式创建配置WebSecurityConfigurerAdapter【功能类似applicationContext-security.xml】:官方对其作用阐述为

The configuration creates a Servlet Filter known as the springSecurityFilterChain which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, etc) within your application. 

package com.cqs.security.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
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 javax.annotation.Resource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true,securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
	protected Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

	@Resource(name = "customUserDetailsService")
	private UserDetailsService userDetailsService;
	@Resource(name = "authProvider")
	private AuthenticationProvider authenticationProvider;


    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//		//加密用户密码
//        ShaPasswordEncoder encoder = new ShaPasswordEncoder();
//		//加载用户信息
//        auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
        auth.userDetailsService(userDetailsService);
		//加载授权信息
		auth.authenticationProvider(authenticationProvider);
    }

	@Autowired
	public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
		//内存中直接用户和授权信息,测试的时候很好用
		auth.inMemoryAuthentication().withUser("test").password("test").roles("Test");
	}


	//权限配置
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
				.authorizeRequests()
				//不拦截的资源
				.antMatchers("/", "/demo2/login.jsp","/images/**","/scripts/**","/styles/**","/user/login.action").permitAll()
				//只有ANONYMOUS角色的用户可访问,Spring Security内置的,未登录之前的用户均是ANONYMOUS
				.antMatchers("/demo2/user/demo3.jsp").hasRole("ANONYMOUS")
				//配置角色:注意Spring Security4之后,这里不必添加ROLE_前缀,Spring Security4会自动补上的。 注意:角色大小写敏感
				.antMatchers("/user/**.action").access("hasRole('ADMIN')  or hasRole('DBA')  or hasRole('TEST') ")
				.antMatchers("/demo2/**.jsp").access("hasRole('ADMIN') or hasRole('TEST')")
				.anyRequest().authenticated()//拦截所有请求
			  .and()
			  	.formLogin().loginPage("/demo2/login.jsp").permitAll()//登陆页面
				//j_username和j_password对用的是登陆页面的对应用户名和密码的name属性而不是ID属性
	  			.usernameParameter("j_username").passwordParameter("j_password")
			  .and()
				.exceptionHandling().accessDeniedPage("/Access_Denied")
			  .and().
				csrf().disable();//关闭csrf
	}

    //struts2必须配置
    @Bean(name="authenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
//
//	@Bean
//	public BCryptPasswordEncoder bCryptPasswordEncoder(){
//		return new BCryptPasswordEncoder();
//	}
}


package com.cqs.security.config;

import com.cqs.demo2.models.user.model.User;
import com.cqs.demo2.models.user.service.UserService;
import com.cqs.security.role.model.Role;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService{

    private Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);

    @Resource(name = "userService")
    private UserService userService;

    @Override  @Transactional(readOnly=true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<User> users = userService.getUserByName(username);
        int num = users.size();
        User user = null;
       if(num == 1) {
           user = users.get(0);
       }
       if(num == 0){
           logger.warn("不存在该用户{}",username);
           user = null;
       }else if (num > 1){
           throw new RuntimeException("存在多个同名的用户");
       }
        logger.debug(ReflectionToStringBuilder.toString(user));
        return new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(),
                user.getStatus().equalsIgnoreCase("active"), true, true, true, getGrantedAuthorities(user));
    }


     
    protected List<GrantedAuthority> getGrantedAuthorities(User user){
        List<GrantedAuthority> authorities = new ArrayList();
        for (Role role :user.getRoles()) {
            logger.debug(ReflectionToStringBuilder.toString(role));
            //注意:这里要ROLE_加上前缀,否则在创建角色而的时候统一加上
            authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
//            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
     
}

package com.cqs.security.config;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component("authProvider")
public class AuthProvider implements AuthenticationProvider {
    private Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
    @Resource
    private CustomUserDetailsService customUserDetailsService;

    @Override
    public Authentication authenticate(Authentication auth)
            throws AuthenticationException {
        String username = auth.getName();
        String password = auth.getCredentials().toString();
        logger.debug("username:{} ,\tpassword:{}",username,password);

        UserDetails user = customUserDetailsService.loadUserByUsername(username);
        logger.debug(ReflectionToStringBuilder.toString(user));
        if (user != null) {
            Authentication token = new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
            return token;
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return false;
    }
}
这里逻辑控制比较简单。以后再慢慢完善吧

2.配置拦截器:既可以在xml中配置,可以使通过Java  Configuration的方式配置,先说XML配置

在web.xml中添加

    <!--Spring Security配置-->
    <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>
有个别资料说springSecurityFilterChain必须配置在struts2Filter的前面,我正好配置在 struts2Filter之前,没试放在struts2Filter之后的场景,反正springSecurityFilterChain在前面是可用的。

3.编写登录方法

 @Resource
 private AuthenticationManager authenticationManager;

public String login() {
        String username = request.getParameter("j_username");
        String password = request.getParameter("j_password");
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
        HttpServletRequest request = ServletActionContext.getRequest();
        // generate session if one doesn't exist
        request.getSession();

        token.setDetails(new WebAuthenticationDetails(request));
        Authentication authenticatedUser = null;
        try {
            authenticatedUser = authenticationManager.authenticate(token);
        } catch (AuthenticationException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
        return SUCCESS;
    }
4.数据:编写User,Role类,为了方便,使用Groovy进行编写

package com.cqs.demo2.models.user.model

import com.cqs.security.role.model.Role
import org.hibernate.annotations.Cascade
import org.hibernate.annotations.CascadeType
import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import org.hibernate.annotations.GenericGenerator
import org.hibernate.validator.constraints.Email

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.JoinTable
import javax.persistence.ManyToMany
import javax.persistence.Table
import javax.persistence.Temporal
import javax.persistence.TemporalType
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size

/**
 * Created by li on 2016/1/12.
 */
@Entity
@Table
class User {
    @Id
    @GenericGenerator(name="idGenerator",strategy = "uuid")
    @GeneratedValue(generator = "idGenerator")
    @Column(length = 32)
    String id;

    @Column(length = 60)
    @Size(max = 60,message = "用户名不能超过60")
    @NotNull
    String name;

    @Column(length = 16)
    @NotNull
    @Size(max = 16)
    String password;



    @Column(length = 50)
    @Email
    String email;

    @Column(length = 10)
    String status;

    public String toString(){
        return "用户名: " + getName() +" 主键: " + getId();
    }

    @Temporal(TemporalType.TIMESTAMP)
    Date createDate;


    @Temporal(TemporalType.TIMESTAMP)
    Date updateDate;

    @Fetch(FetchMode.SUBSELECT)
    @ManyToMany(fetch = FetchType.EAGER)
    @Cascade(value = CascadeType.SAVE_UPDATE)
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    List<Role> roles ;
}

package com.cqs.security.role.model

import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import org.hibernate.annotations.GenericGenerator

import javax.persistence.Column
import javax.persistence.ElementCollection
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.Table

/**
 * Created by li on 2016/1/8.
 */
@Entity
@Table
class Role {
    @Id
    @GenericGenerator(name="idGenerator",strategy = "uuid")
    @GeneratedValue(generator = "idGenerator")
    @Column(length = 32,nullable = false)
    String id;
    @Column(length = 30,nullable = false)
    String name;

}

Role和User编写比较粗糙。 通过Junit4插入几条模拟数据。

package com.cqs.demo2.models.user.service;

import com.cqs.demo2.models.user.model.User;
import com.cqs.security.role.model.Role;
import config.BaseTestConfig;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.junit.Before;
import org.junit.Test;

import javax.annotation.Resource;
import java.util.*;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.*;

public class UserServiceTest extends BaseTestConfig {

    @Resource
    private UserService userService;
    private User user = new User();
    private List<Role> roles = new ArrayList<>();

    String[] usersName = {
        "ADMIN","CUSTOMER","DBA"
    };


   void initRoles(int i){
        Role role = new Role();
        role.setName(usersName[i]);
        roles.add(role);

    }

    public void initUser(int num){
        roles.clear();
        user.setName(usersName[num].toLowerCase());
        user.setEmail("fhdjkasj@163.com");
        user.setCreateDate(new Date());
        user.setUpdateDate(new Date());
        user.setPassword("1");
        user.setStatus(Math.random() > 0.5 ? "active" : "inactive");
        initRoles(num);
        user.setRoles(roles);
    }


    @Test
    public void testCreate() throws Exception {
        for (int i = 0; i < usersName.length; i++) {
            initUser(i);
            userService.create(user);
        }
    }
}
数据库为


5调试:为了调试方便,需将Spring Security的详细信息打印出来。在log4j.properties添加

log4j.logger.org.springframework.security=DEBUG, STDOUT
6.编写登录等页面
<%@ page contentType="text/html;charset=UTF-8" language="java"  isELIgnored="false" %>
<%@include file="./commons/taglib.jsp"%>

<!DOCTYPE  html>
<html lang="zh-CN">
<head>
    <script type="text/ecmascript" src="${ctx}/scripts/jquery-1.11.0.min.js"></script>
    <script type="text/ecmascript" src="${ctx}/scripts/plugins/boostrap/bootstrap.min.js"></script>
    <script type="text/ecmascript" src="${ctx}/scripts/plugins/boostrap/bootstrap-datetimepicker.js"></script>
    <script type="text/javascript" src="${ctx}/scripts/commoms/math.js" ></script>
    <script type="text/javascript">
        $().ready(function(){
            console.log("Hello User123");
        });
    </script>
    <link rel="stylesheet" type="text/css" media="screen" href="${ctx}/styles/plugins/boostrap/bootstrap.min.css" />
</head>

<body>
<div class="container">
    <h1>
        欢迎来到登陆界面
    </h1>


    <form  class="form-horizontal" action="${ctx}/user/login.action" method = "post" style="width: 35%;" >
        <input type="hidden"  name="${_csrf.parameterName}"   value="${_csrf.token}"/>
        <div class="form-group">
            <label for="name" >
                用户名:
            </label>
            <%--<input name="j_username" id="name" value="admin" placeholder="userName" class="form-control"/>--%>
            <s:textfield name="j_username" id="name" value="admin" placeholder="userName" class="form-control" autofocus="autofocus" />
        </div>
        <div class="form-group">
            <label for="password" >
                密码:
            </label>
            <s:password name="j_password" id="password" value="admin" class="form-control"  />
            <%--<input name="j_password"  id="password" value="admin"  placeholder="password" class="form-control" />--%>
        </div>

        <div class="form-group">
            <%--<input type="submit" value="登录" class="form-control btn-default"/>--%>
                <s:submit value="登录" class="form-control btn-default"/>
        </div>
    </form>
</div>
</body>
</html>
其他页面就不粘贴了,运行程序,进入登录页面,并使用admin账号登陆


成功登陆


 查看后台日志


可知是通过ROLE_ADMIN角色进行访问的。现在通过没激活的DBA账号访问


查看后台


现在使用customer的账号登录,之前所有的/user/**.action并没有对ROLE_CUSTOMER角色授权,所以登陆后


没有数据,也就是action方法没有执行,查看后台日志


符合预期。

现在只是完成了先对简单的功能,还有如用户密码加密,对方法权限控制,csrf允许等等均还没弄,以后有时间再慢慢研究吧。

参考:Spring Security Reference

Spring Security 4 Method security using @PreAuthorize,@PostAuthorize, @Secured, EL

Spring Security 4 Hibernate Integration Annotation+XML Example


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值