Spring boot security

  本案例通过mybatis为持久层,自定义了用户和配套权限,在请求Spring boot webcontroller方法时做不同权限的控制。代码在https://github.com/yejingtao/forblog/tree/master/mydemo-security。

  用户权限初始化脚本:

--create schema
create schema schema3;

--create sysuser table
--password length shoud be larger than 60
create table schema3.sysuser(
id int(10) not null AUTO_INCREMENT,
username varchar(10) not null,
password varchar(100) not null,
primary key (id)
);

--create sysrole table
create table schema3.sysauthority(
id int(10) not null AUTO_INCREMENT,
name varchar(20) not null,
description varchar(20),
primary key (id)
);

--create bridge table
create table schema3.r_user_authority(
user_id int(10) not null,
authority_id int(10) not null
);

--init dml
insert into schema3.sysauthority(name,description) values("authority_a","for role a test");
insert into schema3.sysauthority(name,description) values("authority_b","for role b test");

--usera password usera123
--userb password userb123
--userab password userab123
--userc password userc123
insert into schema3.sysuser(username,password) values("usera","$2a$10$DbCNZwHe/OwynRDMsNU7qO9QkpL0c5ZxWypoBf7QAOBXFCjkC6LZC");
insert into schema3.sysuser(username,password) values("userb","$2a$10$ab0wFJHZx.x4dp..ZaeC8ePt2cSmYVXbNBFDVZnTD133xx.Qsb05G");
insert into schema3.sysuser(username,password) values("userab","$2a$10$DdKeZYGBi1msdaXKDjkreOO2eIczINK8.QkQubpSStukFZRHumvTK");
insert into schema3.sysuser(username,password) values("userc","$2a$10$40JDO3oL9uSi68r4GgRSae7/f2Arq2KT3zQ2ZzyNhA6bUKrodOyDS");

insert into schema3.r_user_authority(user_id,authority_id) values((select id from schema3.sysuser where username="usera"),(select id from schema3.sysauthority where name="authority_a"));
insert into schema3.r_user_authority(user_id,authority_id) values((select id from schema3.sysuser where username="userb"),(select id from schema3.sysauthority where name="authority_b"));
insert into schema3.r_user_authority(user_id,authority_id) values((select id from schema3.sysuser where username="userab"),(select id from schema3.sysauthority where name="authority_a"));
insert into schema3.r_user_authority(user_id,authority_id) values((select id from schema3.sysuser where username="userab"),(select id from schema3.sysauthority where name="authority_b"));





  先看Pom依赖:

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.2.1</version>
		</dependency>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

起权限作用的包是spring-boot-starter-security

  再看application.properties配置,没有特别之处。

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://192.168.226.130:3306/schema3
    username: root
    password: admin123
    driver-class-name: com.mysql.jdbc.Driver
  jpa:
    database: MYSQL
    show-sql: true
  application:
    name: mydemo
logging:
  file: ./spring-boot.log
  level:
    org.springframework.web: DEBUG
    com.mydemo.dao: DEBUG

log级别的调整是为了打印mybatissql语句方便调试。

  再看主程序Application,内容比较多,我们一一详解。

package com.mydemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.bcrypt.BCryptPasswordEncoder;

import com.mydemo.service.MybatisUserDetailsService;

@SpringBootApplication
@MapperScan("com.mydemo.dao")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

	@Configuration
	@EnableWebSecurity
	protected static class webSecurityConfig extends WebSecurityConfigurerAdapter {
		@Autowired
		private MybatisUserDetailsService userDetailsService;// 这个名字没得挑,userDetailsService是自定义鉴权的名字。

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			// @formatter:off
			http.authorizeRequests().antMatchers("/", "/hello").permitAll()
					// .antMatchers("/forRoleA/**").access("hasAuthority('authority_a')")
					// .antMatchers("/forRoleB/**").access("hasAuthority('authority_b')")
					// .antMatchers("/forRoleA/**").hasAuthority("authority_a")
					// .antMatchers("/forRoleB/**").hasAuthority("authority_b")
					.and().formLogin();
			/**
			 * .loginPage("/login") .failureUrl("/login?error") .permitAll() //登录页面用户任意访问
			 * 
			 * .and() .logout().permitAll(); //注销行为任意访问
			 */
		}

		// 验证时给密码加密,因为数据库里的也是BCryptPasswordEncoder加密的
		@Override
		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
			auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
		}

		@Bean
		@Override
		public AuthenticationManager authenticationManagerBean() throws Exception {
			return super.authenticationManagerBean();
		}
	}

}

    @MapperScan("com.mydemo.dao")为了指定mybatismapping配置的位置;@EnableGlobalMethodSecurity(prePostEnabled= true)不是必须的,先忽略,后面再细讲。

  启动web权限控制需要@EnableWebSecurity注解,具体配置需要继承WebSecurityConfigurerAdapter。其中userDetailsService是自定义用户和权限控制的服务,必须实现UserDetailsService,被DaoAuthenticationProvider调用。

    configure(AuthenticationManagerBuilder auth)定义了用户密码的验证和密码的加密方式,这里我采用的是目前最安全的BCrypt加密方式。最开始流行的是MD5加密和哈希,安全性实在太差,容易被人查表法破解(将一些比较常用的密码的哈希值算好,然后建立一张表,根据数据库里hash值反向查密码),后来升级为Hash+Salt加密(用户的密码就分为了盐和密文两部分,其中盐是注册时随机生成的,越长越好),安全级别提高不少,但是同样存在数据库中密文和盐一起泄露的可能性,攻击者拿到密文和盐暴力破解也是几天的事。Bcrypt加密就是把salt随机混入了最终的加密,不再单独保存salt,通过增加计算密文的成本来换取安全。使用Bcrypt,默认设置下登陆时间会增加5个数量级,破解成本也会增加5个数量级。这里注意数据存的密码列要足够大,至少要varchar60才能存的下Bcrypt密码。TestPasswordGet提供了加密的密码。

对具体的controller方法进行权限的控制有2种方式,代码示例中都已提供。

方式一:静态配置到configure(HttpSecurity http)中,其中注释的两种方法是等效的

// .antMatchers("/forRoleA/**").access("hasAuthority('authority_a')")
					// .antMatchers("/forRoleB/**").access("hasAuthority('authority_b')")
					// .antMatchers("/forRoleA/**").hasAuthority("authority_a")
					// .antMatchers("/forRoleB/**").hasAuthority("authority_b")

方式二:通过@PreAuthorize配置,详见controller代码,请注意如果要用注释法Application中要添加@EnableGlobalMethodSecurity(prePostEnabled = true)

    .and().formLogin()表示权限不足时自动跳转到登录页面,如果后面增加.loginPage("/login")标识自定义了登录页面,根据是jsp还是模板去对应目录里去找(http://blog.csdn.net/yejingtao703/article/details/77414711),如果没有.logPage()Spring自己提供了默认的登陆页面

    MybatisUserDetailsService中核心方法是接口UserDetailsService中的loadUserByUsername方法,代码比较容易读懂,给登录用户组装相应的权限。

package com.mydemo.service;

import java.util.ArrayList;
import java.util.List;

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.Service;

import com.mydemo.dao.AuthorityRepository;
import com.mydemo.dao.UserRepository;
import com.mydemo.entity.SysAuthority;
import com.mydemo.entity.SysUser;

@Service
public class MybatisUserDetailsService implements UserDetailsService{
	
	@Autowired
	private AuthorityRepository authorityRepository;
	
	@Autowired
	private UserRepository userRepository;
	
	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
		//先判断登陆用户是否存在
		SysUser user = userRepository.findByUserName(userName);
		if(user==null) {
			throw new UsernameNotFoundException("Cannot find user by username: "+userName);
		}
		//登录用户存在的情况加载用户的权限
		/**
		SecurityUser securityUser = new SecurityUser(user);
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return securityUser;
        */
		
		List<SysAuthority> roleList = authorityRepository.findByUserId(user.getId());
		List<GrantedAuthority> grantedAuthorities = new ArrayList <GrantedAuthority>();
		if(roleList!=null && roleList.size()>0) {
			roleList.forEach(r->grantedAuthorities.add(new SimpleGrantedAuthority(r.getName())));
		}
		return new User(user.getUserName(),user.getPassword(),grantedAuthorities);
	}

}

  测试起来很简单,hello可以随意进入,roleTestMothedA只有带有A权限的才可以进入,roleTestMothedB只有带有B权限的才可以进入,我们准备了4个用户供测试,usera、userb、userab、userc都试着操作下。

如果直接访问/hello:


如果直接访问/forRoleA或者/forRoleB,会跳转到登陆页面:


输入密码密码:

输入正确后:


输入错误后:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值