社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
数据库设计
说明:
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
import 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;
}
}
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
}
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;
}
}
@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;
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!