java中Spring Security的实例详解
java中Spring Security的实例详解
spring security是一个多方面的安全认证框架,提供了基于JavaEE规范的完整的安全认证解决方案。并且可以很好与目前主流的认证框架(如CAS,中央授权系统)集成。使用spring security的初衷是解决不同用户登录不同应用程序的权限问题,说到权限包括两部分:认证和授权。认证是告诉系统你是谁,授权是指知道你是谁后是否有权限访问系统(授权后一般会在服务端创建一个token,之后用这个token进行后续行为的交互)。
spring security提供了多种认证模式,很多第三方的认证技术都可以很好集成:
- Form-based authentication (用于简单的用户界面)
- OpenID 认证
- Authentication based on pre-established request headers (such as Computer - Associates Siteminder)根据预先建立的请求头进行验证
- JA-SIG Central Authentication Service ( CAS, 一个开源的SSO系统)
- Java Authentication and Authorization Service (JAAS)
这里只列举了部分,后面会重点介绍如何集成CAS,搭建自己的认证服务。
在spring boot项目中使用spring security很容易,这里介绍如何基于内存中的用户和基于数据库进行认证。
准备
pom依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>1.5.1.RELEASE</version> </dependency>
配置:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("rhwayfun").password("1209").roles("USERS") .and().withUser("admin").password("123456").roles("ADMIN"); //auth.jdbcAuthentication().dataSource(securityDataSource); //auth.userDetailsService(userDetailsService()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests()//配置安全策略 //.antMatchers("/","/index").permitAll()//定义/请求不需要验证 .anyRequest().authenticated()//其余的所有请求都需要验证 .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/index") .permitAll() .and() .logout() .logoutSuccessUrl("/login") .permitAll();//定义logout不需要验证 http.csrf().disable(); } }
这里需要覆盖WebSecurityConfigurerAdapter的两个方法,分别定义什么请求需要什么权限,并且认证的用户密码分别是什么。
@Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { /** * 统一注册纯RequestMapping跳转View的Controller */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("/login"); } }
添加登录跳转的URL,如果不加这个配置也会默认跳转到/login下,所以这里还可以自定义登录的请求路径。
登录页面:
<!DOCTYPE html> <%--<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>--%> <html> <head> <meta charset="utf-8"> <title>Welcome SpringBoot</title> <script src="/js/jquery-3.1.1.min.js"></script> <script src="/js/index.js"></script> </head> <body> <form name="f" action="/login" method="post"> <input id="name" name="username" type="text"/><br> <input id="password" name="password" type="password"><br> <input type="submit" value="login"> <input name="_csrf" type="hidden" value="${_csrf}"/> </form> <p id="users"> </p> <script> $(function () { $('[name=f]').focus() }) </script> </body> </html>
基于内存
SecurityConfig这个配置已经是基于了内存中的用户进行认证的,
auth.inMemoryAuthentication()//基于内存进行认证 .withUser("rhwayfun").password("1209")//用户名密码 .roles("USERS")//USER角色 .and() .withUser("admin").password("123456").roles("ADMIN");//ADMIN角色
访问首页会跳转到登录页面这成功,使用配置的用户登录会跳转到首页。
基于数据库
基于数据库会复杂一些,不过原理是一致的只不过数据源从内存转到了数据库。从基于内存的例子我们大概知道spring security认证的过程:从内存查找username为输入值得用户,如果存在着验证其角色时候匹配,比如普通用户不能访问admin页面,这点可以在controller层使用@PreAuthorize("hasRole('ROLE_ADMIN')")实现,表示只有admin角色的用户才能访问该页面,spring security中角色的定义都是以ROLE_开头,后面跟上具体的角色名称。
如果要基于数据库,可以直接指定数据源即可:
auth.jdbcAuthentication().dataSource(securityDataSource);
只不过数据库标是spring默认的,包括三张表:users(用户信息表)、authorities(用户角色信息表)
以下是查询用户信息以及创建用户角色的SQL(部分,详细可到JdbcUserDetailsManager类查看):
public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)"; public static final String DEF_INSERT_AUTHORITY_SQL = "insert into authorities (username, authority) values (?,?)"; public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?";
那么,如果要自定义数据库表这需要配置如下,并且实现UserDetailsService接口:
auth.userDetailsService(userDetailsService()); @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(); }
CustomUserDetailsService实现如下:
@Service public class CustomUserDetailsService implements UserDetailsService { /** Logger */ private static Logger log = LoggerFactory.getLogger(CustomUserDetailsService.class); @Resource private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (StringUtils.isBlank(username)) { throw new IllegalArgumentException("username can't be null!"); } User user; try { user = userRepository.findByUserName(username); } catch (Exception e) { log.error("读取用户信息异常,", e); return null; } if (user == null) { return null; } List<UserAuthority> roles = userRepository.findRoles(user.getUserId()); List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (UserAuthority role : roles) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(String.valueOf(role.getAuthId())); authorities.add(authority); } return new org.springframework.security.core.userdetails.User(username, "1234", authorities); } }
我们需要实现loadUserByUsername方法,这里面其实级做了两件事:查询用户信息并返回该用户的角色信息。
数据库设计如下:
数据库设计
g_users:用户基本信息表
g_authority:角色信息表
r_auth_user:用户角色信息表,这里没有使用外键约束。
使用mybatis generator生成mapper后,创建数据源SecurityDataSource。
@Configuration @ConfigurationProperties(prefix = "security.datasource") @MapperScan(basePackages = DataSourceConstants.MAPPER_SECURITY_PACKAGE, sqlSessionFactoryRef = "securitySqlSessionFactory") public class SecurityDataSourceConfig { private String url; private String username; private String password; private String driverClassName; @Bean(name = "securityDataSource") public DataSource securityDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean(name = "securityTransactionManager") public DataSourceTransactionManager securityTransactionManager() { return new DataSourceTransactionManager(securityDataSource()); } @Bean(name = "securitySqlSessionFactory") public SqlSessionFactory securitySqlSessionFactory(@Qualifier("securityDataSource") DataSource securityDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(securityDataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(DataSourceConstants.MAPPER_SECURITY_LOCATION)); return sessionFactory.getObject(); } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } }
那么DAO UserRepository就很好实现了:
public interface UserRepository{ User findByUserName(String username); List<UserAuthority> findRoles(int userId); }
在数据库插入相关数据,重启项目。仍然访问首页跳转到登录页后输入数据库插入的用户信息,如果成功跳转到首页这说明认证成功。
如有疑问请留言或者到本站社区交流讨论,感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!