至于什么是Spring security ,主要两个做用,用户认证和受权。即咱们常说的,用户只有登陆了才能进行其余操做,没有登陆的话就重定向到登陆界面。有的用户有权限执行某一操做,而有的用户不能执行则是受权。算是一个项目安全框架。和shiro 框架同样。两者的不一样你们能够百度小。Spring security 是Spring家族的一员,因此Springboot算是对Spring security 进行的自然的支持。java
之因此这样说,spring security 被人诟病的配置繁琐复杂,在springboot中变的简单起来。若是咱们只是demo 效果,能够作到0配置实现。mysql
下面咱们就一块儿来见识一下吧linux
咱们在pom.xml 文件中引入Spring security 的stattergit
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
咱们先来0配置的看看。引入依赖之后,咱们建立一个HelloController 内容以下:程序员
@RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello world"; } }
而后咱们启动项目,按照咱们正常的理解,直接访问github
localhost:8080/hello
会返回hello world 。但结果倒是重定向到了/login 。下面的界面是Spring security 自带的。
其实上面能够看到,Spring security 已经起做用了,没有登陆不能访问 /hello 接口。web
默认的用户名为 user;
密码在咱们项目启动时控制台有打印,每次都会不同,随机生成的。
咱们输入帐号密码,再试试
能够看到,在登陆以后,咱们在请求 /hello 会直接返回hello world , 那是否是只要登陆一次,后面就能够一直访问呢?固然不是的,登陆成功以后,会将信息保存在session 中,再登陆的时候,就会经过session 校验,这样就能够访问到了,当session过时获取咱们手动清理掉后,就须要从新登陆了。咱们来试试。打开控制台,application 中的cookies 中的jsessionid 清理掉。
咱们接着请求试试,能够发现删除后,就会从新回到登陆界面。spring
上面咱们使用的默认的用户名和密码,可是实际上咱们确定不会这么作的,上面只是说明springboot 彻底的集成了Spring security 。下面咱们先来简单的配置用户名密码,之因此这样说,由于咱们实际过程当中应该仍是不会这么用的。之因此要讲,让你们了解的更全面,也为下面铺垫。sql
首先咱们来简单的,咱们能够直接在application.properties 中配置用户名和密码。来代替默认用户名和密码的效果。数据库
spring.security.user.name=quellanan spring.security.user.password=123456 spring.security.user.roles=admin
分别是设置用户名,密码,角色。咱们这里暂时只用了用户认证,因此角色设不设置无所谓。配置好这些以后咱们重启项目在界面上试试再。
没有问题,可是没有什么用,咱们实际中是不会这么干的吧。
在内存中配置的话,相对来讲要复杂点,咱们建立一个config 包,在包下建立SecurityConfig 类继承 WebSecurityConfigurerAdapter
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(passwordEncoder()) // 指定加密方式 .withUser("qaz").password(passwordEncoder().encode("123456")).roles("admin") .and() .withUser("test").password(passwordEncoder().encode("123456")).roles("USER"); } @Bean public PasswordEncoder passwordEncoder() { // BCryptPasswordEncoder:Spring Security 提供的加密工具 return new BCryptPasswordEncoder(); } }
这里咱们重写了configure(AuthenticationManagerBuilder auth) 方法,就是将定义的用户配置到内存中。这里有一个问题须要说明一下,就是这里配置的话,密码须要用BCryptPasswordEncoder 加密。若是不加密的话,项目编译启动不会报错,可是登录的时候就会提示帐号密码错误。
还有一个问题就是,若是咱们在这配置了,那咱们在application.peoperties 中配置的就会失效。
上面说的这两种方法,其实都是不经常使用的,咱们在实际项目中根本不会在项目中写死用户信息的。基本上都是存在数据库中。因此下面咱们就开始讲解咱们最经常使用的模式吧。
因为这一类,涉及的较多,就单独一级标题出来,不放在二级标题里面了。
既然是用到数据库,项目中天然要引入数据的配置啦,我这里用的是mysql 和mybatis.
这是整个项目成型后的目录结构,先放出来,你们内心有底,而后一步一步的来。
简单的三张表,user,roles,roles_user 。
下面是 sql。直接执行就能够
/* Date: 2017-12-26 18:36:12 */ CREATE DATABASE `quellanan` DEFAULT CHARACTER SET utf8; USE `quellanan`; SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for roles -- ---------------------------- DROP TABLE IF EXISTS `roles`; CREATE TABLE `roles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of roles -- ---------------------------- INSERT INTO `roles` VALUES ('1', '超级管理员'); INSERT INTO `roles` VALUES ('2', '普通用户'); INSERT INTO `roles` VALUES ('3', '测试角色1'); INSERT INTO `roles` VALUES ('4', '测试角色2'); INSERT INTO `roles` VALUES ('5', '测试角色3'); -- ---------------------------- -- Table structure for roles_user -- ---------------------------- DROP TABLE IF EXISTS `roles_user`; CREATE TABLE `roles_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rid` int(11) DEFAULT '2', `uid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `rid` (`rid`), KEY `roles_user_ibfk_2` (`uid`), CONSTRAINT `roles_user_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `roles` (`id`), CONSTRAINT `roles_user_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=131 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of roles_user -- ---------------------------- INSERT INTO `roles_user` VALUES ('1', '1', '1'); INSERT INTO `roles_user` VALUES ('2', '2', '2'); INSERT INTO `roles_user` VALUES ('3', '3', '3'); INSERT INTO `roles_user` VALUES ('4', '1', '4'); -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(64) DEFAULT NULL, `nickname` varchar(64) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `enabled` tinyint(1) DEFAULT '1', `email` varchar(64) DEFAULT NULL, `userface` varchar(255) DEFAULT NULL, `regTime` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES ('1', 'quellanan', '', '$2a$10$Hv0YGLi/siOswCTP236MtOTWbClcM6rN1LCyqwfRmrwCJZqXHsj5a', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('2', 'qaz', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('3', 'wsx', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('4', 'test', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); SET FOREIGN_KEY_CHECKS=1;
咱们首先在原先pom 文件基础上增长,以下依赖。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
前面三个是mysql 和mybatis的依赖。lombok 是一个工具类插件。
同时咱们须要修改一下pom 文件中的build ,否则咱们项目可能会找不到mybatis 的xml文件。
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/quellanan?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5
这里若是想要打印mybatis 的sql 日志。能够添加一个mybatis-config.xml文件,和application.properties 同目录
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> </configuration>
并在application.properties 中加上
mybatis.config-location=classpath:/mybatis-config.xml
咱们在entry 包下建立 RoleEntry。代码以下:
@Getter @Setter public class RoleEntry { private Long id; private String name; }
咱们在建立 UserEntry ,可是UserEntry 比较特殊,由于咱们须要使用Spring security 。因此这里,UserEntry 须要实现 UserDetails。
代码以下:
@Setter @Getter public class UserEntry implements UserDetails { private Long id; private String username; private String password; private String nickname; private boolean enabled; private List<RoleEntry> roles; private String email; private String userface; private Timestamp regTime; /** * 获取角色权限 * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); for (RoleEntry role : roles) { authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())); } return authorities; } /** * 获取密码 * @return */ @Override public String getPassword() { return password; } /** * 获取用户名 * @return */ @Override public String getUsername() { return username; } /** * 用户帐号是否过时 */ @Override public boolean isAccountNonExpired() { return true; } /** * 用户帐号是否被锁定 */ @Override public boolean isAccountNonLocked() { return true; } /** * 用户密码是否过时 */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 用户是否可用 */ @Override public boolean isEnabled() { return enabled; } }
能够看到,基本上都是重写的方法。也比较简单。
这里我将xml 文件和接口放在一块儿了,大家也能够在resources 中建立一个mapper,将xml 文件放在哪里。
mapper层没有什么好说的,是mybatis 的一些知识,咱们这里讲代码贴出来。
@Mapper public interface RolesMapper { List<RoleEntry> getRolesByUid(Long uid); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zlflovemm.security.mapper.RolesMapper"> <select id="getRolesByUid" parameterType="long" resultType="com.zlflovemm.security.entry.RoleEntry"> SELECT r.* FROM roles r,roles_user ru WHERE r.`id`=ru.`rid` AND ru.`uid`=#{uid} </select> </mapper>
@Mapper public interface UserMapper { UserEntry loadUserByUsername(@Param("username") String username); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zlflovemm.security.mapper.UserMapper"> <select id="loadUserByUsername" resultType="com.zlflovemm.security.entry.UserEntry"> SELECT * FROM user WHERE username=#{username} </select> </mapper>
在service 层咱们要注意一点,咱们须要实现 UserDetailsService 接口。
咱们先建立一个UserService 继承 UserDetailsService。而后建立一个UserServiceImpl 来时实现UserService 从而达到实现UserDetailsService的目的。这样作是为了保证项目结构的统一层次。
public interface UserService extends UserDetailsService { }
@Service @Slf4j @Transactional public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Autowired RolesMapper rolesMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { UserEntry user = userMapper.loadUserByUsername(s); if (user == null) { //避免返回null,这里返回一个不含有任何值的User对象,在后期的密码比对过程当中同样会验证失败 return new UserEntry(); } //查询用户的角色信息,并返回存入user中 List<RoleEntry> roles = rolesMapper.getRolesByUid(user.getId()); user.setRoles(roles); return user; } }
能够看到,主要是为了实现 loadUserByUsername的方法。在这个方法中咱们 loadUserByUsername和getRolesByUid 就是咱们在mapper 定义的查询数据库数据的方法。
前面作了这么多,其实都是准备工做,主要的目的就是提供一个Bean 。作完上面这些,咱们再回到 SecurityConfig 中,其实咱们如今须要修改的不多了。
咱们将用户写在内存的方法注释掉。经过数据库查询。
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Bean public PasswordEncoder passwordEncoder() { // BCryptPasswordEncoder:Spring Security 提供的加密工具 return new BCryptPasswordEncoder(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService) .passwordEncoder(passwordEncoder());//passwoldEncoder是对密码的加密处理,若是user中密码没有加密,则能够不加此方法。注意加密请使用security自带的加密方式。 } }
能够和开始的 SecurityConfig 文件对比下,其实你就是多了一个userService,而后configure(AuthenticationManagerBuilder auth)中是经过userService 进行校验的。
好了,其实到这里,咱们就已经完成了,咱们启动项目,就能够看到和以前写在内存中达到同样的效果。
觉得到这就完了,其实还有一点哈哈。咱们如今是全部的接口都须要先登陆才能访问,没有登陆的话就跳转到login界面。实际上咱们确定有些是不须要认证也能够访问的,好比如下静态文件或者注册的请求。
因此咱们仍是要配置一下过滤。
其实也很简单,同样的在 SecurityConfig 文件中 重写 configure(HttpSecurity http) 方法。
这里我直接参考官网上的。
https://spring.io/guides/gs/securing-web/
该configure(HttpSecurity)方法定义应保护哪些URL路径,不该该保护哪些URL路径。具体而言,“ /”和“ / home”路径配置为不须要任何身份验证。全部其余路径必须通过验证。
用户成功登陆后,他们将被重定向到以前要求身份验证的页面。有一个由指定的自定义“ /登陆”页面loginPage(),每一个人均可以查看它。
咱们代码中 把 loginPage("/login") 注释掉就行了,若是不注释的话,就须要咱们本身写login 界面和请求。咱们这里就用框架自带的。
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/hello").permitAll() .anyRequest().authenticated() .and() .formLogin() //.loginPage("/login") .permitAll() .and() .logout() .permitAll(); }
这样配置就说明 /hell 和 / 请求不会拦截,其余的请求,须要先登陆才能访问。
为了更方便的看到效果,咱们在HelloController 中再加两个方法
@RequestMapping("/hello2") public String hello2(){ return "hello adada"; } @RequestMapping("/") public String hello3(){ return " qazqeee"; } }
如今咱们启动来看下效果。
证实咱们配置的过滤是有效果的。
到此算是差很少结束了,其实还有不少知识点,不是一篇文章能讲完的,这里算是抛转引玉,但愿对你们有帮助。后面我也会持续更新
好了,源码我上传到github 上啦
https://github.com/QuellanAn/security
后续加油♡
欢迎你们关注我的公众号 "程序员爱酸奶"
分享各类学习资料,包含java,linux,大数据等。资料包含视频文档以及源码,同时分享本人及投递的优质技术博文。
若是你们喜欢记得关注和分享哟❤