springboot+springSecurity+mybatis實現許可權管理
資料庫設計
說明:
1.使用者可以對應多個角色,角色可以對應多個許可權;
2.PermissionAccess對應menu,button,action;
配置檔案
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.iflytek</groupId> <artifactId>security_demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>security_demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <repositories> <repository> <id>opensesame</id> <name>Alibaba OpenSource Repsoitory</name> <url>http://code.alibabatech.com/mvn/releases/</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.18</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.31</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
springboot+mybatis配置
application.properties
spring.datasource.name = security spring.datasource.url = jdbc:mysql://localhost:3306/security?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC spring.datasource.username = root spring.datasource.password = spring.datasource.type = com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name = com.mysql.jdbc.Driver spring.datasource.filters = stat spring.datasource.maxActive = 20 spring.datasource.initialSize = 1 spring.datasource.maxWait = 60000 spring.datasource.minIdle = 1 spring.datasource.timeBetweenEvictionRunsMillis = 60000 spring.datasource.minEvictableIdleTimeMillis = 300000 spring.datasource.validationQuery = select 'x' spring.datasource.testWhileIdle = true spring.datasource.testOnBorrow = false spring.datasource.testOnReturn = false spring.datasource.poolPreparedStatements = true spring.datasource.maxOpenPreparedStatements = 20 server.port=8888
DruidConfig.java
MapperScannerConfig.javaimport com.alibaba.druid.pool.DruidDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.sql.SQLException; /** * Created by jjpeng on 2017/5/10. */ @Configuration public class DruidConfig { private Logger logger = LoggerFactory.getLogger(getClass()); @Value("${spring.datasource.url}") private String dbUrl; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Value("${spring.datasource.initialSize}") private int initialSize; @Value("${spring.datasource.minIdle}") private int minIdle; @Value("${spring.datasource.maxActive}") private int maxActive; @Value("${spring.datasource.maxWait}") private int maxWait; @Value("${spring.datasource.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("${spring.datasource.minEvictableIdleTimeMillis}") private int minEvictableIdleTimeMillis; @Value("${spring.datasource.validationQuery}") private String validationQuery; @Value("${spring.datasource.testWhileIdle}") private boolean testWhileIdle; @Value("${spring.datasource.testOnBorrow}") private boolean testOnBorrow; @Value("${spring.datasource.testOnReturn}") private boolean testOnReturn; @Value("${spring.datasource.poolPreparedStatements}") private boolean poolPreparedStatements; @Value("${spring.datasource.filters}") private String filters; @Bean @Primary public DruidDataSource druidDataSource(){ DruidDataSource datasource = new DruidDataSource(); datasource.setUrl(this.dbUrl); datasource.setUsername(username); datasource.setPassword(password); datasource.setDriverClassName(driverClassName); datasource.setInitialSize(initialSize); datasource.setMinIdle(minIdle); datasource.setMaxActive(maxActive); datasource.setMaxWait(maxWait); datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setValidationQuery(validationQuery); datasource.setTestWhileIdle(testWhileIdle); datasource.setTestOnBorrow(testOnBorrow); datasource.setTestOnReturn(testOnReturn); datasource.setPoolPreparedStatements(poolPreparedStatements); try { datasource.setFilters(filters); } catch (SQLException e) { logger.error("druid configuration initialization filter", e); } return datasource; } }
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by admin on 2017/5/11.
*/
@Configuration
@AutoConfigureAfter(MybatisConfig.class)
public class MapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage("com.iflytek.dao");
return mapperScannerConfigurer;
}
}
MybatisConfig.java
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class MybatisConfig implements TransactionManagementConfigurer{
@Autowired
private DataSource dataSource;
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(){
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("com.iflytek.model");
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath:mapper/*"));
return bean.getObject();
} catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
@Override
public PlatformTransactionManager annotationDrivenTransactionManager(){
return new DataSourceTransactionManager(dataSource);
}
}
Security配置
SecurityConfig.java
import com.iflytek.service.MyFilterSecurityInterceptor;
import com.iflytek.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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.web.access.intercept.FilterSecurityInterceptor;
/**
* Created by admin on 2017/4/6.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
@Bean
UserDetailsService myUserDetailsService(){
return new MyUserDetailsService();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated() //任何請求,登入後可以訪問
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.permitAll() //登入頁面使用者任意訪問
.and()
.logout().permitAll(); //登出行為任意訪問
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class).csrf().disable();;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**", "/**/favicon.ico");
//防止攔截css,js,image檔案
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService());
//登入驗證,繫結自定義的UserDetailServiceHolder
}
SpringSecurity登入驗證
1.自定義類實現UserDetailsService介面機器loadUserByUserName方法;
2.SpringSecurity的authenticationProcessingFilter攔截器呼叫AuthenticationManager,UserDetailsService拿到使用者資訊後,authenticationManager對比使用者名稱密碼,如果通過了,則相當於通過了AuthenticationProcessingFilter攔截器,也就是登入驗證通過。
3.登入 獲取登入使用者的使用者許可權,將使用者資訊和許可權資訊儲存在SecurityContextHolder中。
myUserDetailsService.java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Autowired
private PermissionDao permissionDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//判斷使用者是否存在
SYS_User u = userDao.findByAccount(s);
if (u == null){
throw new UsernameNotFoundException(s+"使用者名稱不存在");
}
//根據使用者獲取許可權
List<SYS_Permission> permissions = permissionDao.findByUserId(u.getUserID());
//定義許可權集合
List<GrantedAuthority> grantedAuthorities = new ArrayList();
//新增許可權到集合中
for (SYS_Permission permission: permissions){
if (permission !=null && permission.getPermissionOperation()==true) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getPermissionAccess() + "_" + permission.getPermissionAccessValue());
grantedAuthorities.add(grantedAuthority);
}
}
//將已登入的使用者資訊儲存到SecurityContext
org.springframework.security.core.userdetails.User user = new User(u.getAccount(),u.getPassword(),u.getStatus(),true,true, true, grantedAuthorities);
return user;
}
}
SpringSecurity資源訪問控制
訪問資源url
↓
AbstractSecurityInterceptor攔截
↓
呼叫FilterInvocationSecurityMetadataSource的方法(資源許可權關聯)來獲取被攔截url所需要的全部許可權
↓
呼叫授權管理器AccessDecisionManager(訪問決策)
↓
通過全域性快取SecurityContextHolder獲取使用者的許可權資訊,decide方法判斷使用者是否有許可權訪問該資源
myFilterSecurityInterceptor.java
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager){
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
//登入後,每次訪問資源都通過這個攔截器攔截
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(servletRequest,servletResponse,filterChain);
invoke(fi);
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void invoke(FilterInvocation fi) throws IOException,ServletException {
//fi裡面有一個被攔截的url
//裡面呼叫MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有許可權
//再呼叫MyAccessDecisionManager的decide方法來校驗使用者的許可權是否足夠
InterceptorStatusToken token = super.beforeInvocation(fi);
try{
//執行下一個攔截器
fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
}finally {
super.afterInvocation(token,null);
}
}
}
myInvocationSecurityMetadataSource.java
@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
@Autowired
private PermissionDao permissionDao;
@Autowired
private MenuDao menuDao;
@Autowired
private ButtonDao buttonDao;
private HashMap<String, Collection<ConfigAttribute>> map =null;
//載入許可權表中所有許可權
public void loadResourceDefine(){
map = new HashMap<>();
Collection<ConfigAttribute> atts;
//獲取所有許可權並儲存
List<SYS_Permission> permissions = permissionDao.findAll();
for(SYS_Permission permission : permissions) {
atts = new ArrayList<>();
ConfigAttribute cfg = new SecurityConfig(permission.getPermissionAccess() + "_" + permission.getPermissionAccessValue());
atts.add(cfg);
//對應的menu或button的url
if (permission.getPermissionAccess().equals("menu")){
SYS_Menu sys_menu = menuDao.findById(permission.getPermissionAccessValue());
map.put(sys_menu.getUrl(),atts);
}else if (permission.getPermissionAccess().equals("button")){
SYS_Button sys_button = buttonDao.findById(permission.getPermissionAccessValue());
map.put(sys_button.getUrl(),atts);
}
}
}
//此方法是為了判定使用者請求是否在許可權表中,如果在許可權表中,則返回給 decide 方法,用來判定使用者是否有此許可權。如果不在許可權表中則放行。
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
if(map ==null) loadResourceDefine();
//object 中包含使用者請求的request 資訊
HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
Iterator<String> ite = map.keySet().iterator();
while (ite.hasNext()){
resUrl = ite.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
myAccessDecisionManager.java
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
// decide 方法是判定是否擁有許可權的決策方法,
//authentication 是UserDetailService中迴圈新增到 GrantedAuthority 物件中的許可權資訊集合.
//object 包含客戶端發起的請求的requset資訊,可轉換為 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 為MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法返回的結果,此方法是為了判定使用者請求的url 是否在許可權表中,如果在許可權表中,則返回給 decide 方法,用來判定使用者是否有此許可權。如果不在許可權表中則放行。
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
if (null == collection || collection.size()<=0){
return;
}
ConfigAttribute c;
String needRole;
Iterator<ConfigAttribute> ite = collection.iterator();
while (ite.hasNext()){
c = ite.next();
needRole = c.getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()){
if (needRole.trim().equals(ga.getAuthority())){
return;
}
}
}
throw new AccessDeniedException("no right");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}