摘要:什么是是一个能够为基于的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它来自于,那么它与整合开发有着天然的优势,目前与对应的开源框架还有。通常大家在做一个后台管理的系统的时候,应该采用判断用户是否登录。
什么是SpringSecurity ?
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
以上来介绍来自wiki,比较官方。
用自己的话 简单介绍一下,Spring Security 基于 Servlet 过滤器链的形式,为我们的web项目提供认证与授权服务。它来自于Spring,那么它与SpringBoot整合开发有着天然的优势,目前与SpringSecurity对应的开源框架还有shiro。接下来我将通过一个简单的例子带大家来认识SpringSecurity,然后通过分析它的源码带大家来认识一下SpringSecurity是如何工作,从一个简单例子入门,大家由浅入深的了解学习SpringSecurity。
通常大家在做一个后台管理的系统的时候,应该采用session判断用户是否登录。我记得我在没有接触学习SpringSecurity与shiro之前。对于用户登录功能实现通常是如下:
public String login(User user, HttpSession session){ //1、根据用户名或者id从数据库读取数据库中用户 //2、判断密码是否一致 //3、如果密码一致 session.setAttribute("user",user); //4、否则返回登录页面 } 对于之后那些需要登录之后才能访问的url,通过SpringMvc的拦截器中的#preHandle来判断session中是否有user对象 如果没有 则返回登录页面 如果有, 则允许访问这个页面。
但是在SpringSecurity中,这一些逻辑已经被封装起来,我们只需要简单的配置一下就能使用。
接下来我通过一个简单例子大家认识一下SpringSecurity
本文基于SpringBoot,如果大家对SpringBoot不熟悉的话可以看看我之前写的SpringBoot入门系列
我使用的是:
SpringBoot 2.1.4.RELEASE
SpringSecurity 5
4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.4.RELEASE com.yukong springboot-springsecurity 0.0.1-SNAPSHOT springboot-springsecurity springboot-springsecurity study 1.8 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.0.1 mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test org.springframework.boot spring-boot-maven-plugin
配置一下数据库 以及MyBatis
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/db_test?useUnicode=true&characterEncoding=utf8 password: abc123 mybatis: mapper-locations: classpath:mapper/*.xml
这里我用的MySQL8.0 大家注意一下 MySQL8.0的数据库驱动的类的包改名了
在前面我有讲过SpringBoot中如何整合Mybatis,在这里我就不累述,有需要的话看这篇文章
user.sql
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT "主键", `username` varchar(32) NOT NULL COMMENT "用户名", `svc_num` varchar(32) DEFAULT NULL COMMENT "用户号码", `password` varchar(100) DEFAULT NULL COMMENT "密码", `cust_id` bigint(20) DEFAULT NULL COMMENT "客户id 1对1", PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
对应的UserMapper.java
package com.yukong.mapper; import com.yukong.entity.User; /** * * @author yukong * @date 2019-04-11 16:50 */ public interface UserMapper { int insertSelective(User record); User selectByUsername(String username); }
UserMapper.xml
id, username, svc_num, `password`, cust_id insert into user username, svc_num, `password`, cust_id, #{username,jdbcType=VARCHAR}, #{svcNum,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{custId,jdbcType=BIGINT},
在这里我们定义了两个方法。
国际惯例ctrl+shift+t创建mapper的测试方法,并且插入一条记录
package com.yukong.mapper; import com.yukong.SpringbootSpringsecurityApplicationTests; import com.yukong.entity.User; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import static org.junit.Assert.*; /** * @author yukong * @date 2019-04-11 16:53 */ public class UserMapperTest extends SpringbootSpringsecurityApplicationTests { @Autowired private UserMapper userMapper; @Test public void insert() { User user = new User(); user.setUsername("yukong"); user.setPassword("abc123"); userMapper.insertSelective(user); } }
运行测试方法,并且成功插入一条记录。
创建UserController.java
package com.yukong.controller; import com.yukong.entity.User; import com.yukong.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author yukong * @date 2019-04-11 15:22 */ @RestController public class UserController { @Autowired private UserMapper userMapper; @RequestMapping("/user/{username}") public User hello(@PathVariable String username) { return userMapper.selectByUsername(username); } }
这个方法就是根据用户名去数据库查找用户详细信息。
启动。因为我们之前插入过一条username=yukong的记录,所以我们查询一下,访问127.0.0.1:8080/user/yukong
[图片上传失败...(image-ea02ac-1554981869345)]
我们可以看到 我们被重定向到了一个登录界面,这也是我们之前引入的spring-boot-security-starter起作用了。
大家可能想问了,用户名跟密码是什么,用户名默认是user,密码在启动的时候已经通过日志打印在控制台了。
现在我们输入用户跟密码并且登录。就可以成功访问我们想要访问的接口。
从这里我们可以知道,我只需要引入了Spring-Security的依赖,它就开始生效,并且保护我们的接口了,但是现在有一个问题就是,它的用户名只能是user并且密码是通过日志打印在控制台,但是我们希望它能通过数据来访问我们的用户并且判断登录。
其实想实现这个功能也很简单。这里我们需要了解两个接口。
UserDetails
UserDetailsService
所以,我们需要将我们的User.java实现这个接口
package com.yukong.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /** * * @author yukong * @date 2019-04-11 16:50 */ public class User implements UserDetails { /** * 主键 */ private Long id; /** * 用户名 */ private String username; /** * 用户号码 */ private String svcNum; /** * 密码 */ private String password; /** * 客户id 1对1 */ private Long custId; public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return false; } @Override public boolean isAccountNonLocked() { return false; } @Override public boolean isCredentialsNonExpired() { return false; } @Override public boolean isEnabled() { return false; } public void setUsername(String username) { this.username = username; } public String getSvcNum() { return svcNum; } public void setSvcNum(String svcNum) { this.svcNum = svcNum; } @Override public Collection extends GrantedAuthority> getAuthorities() { // 这里我们没有用到权限,所以返回一个默认的admin权限 return AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Long getCustId() { return custId; } public void setCustId(Long custId) { this.custId = custId; } }
接下来我们再看看UserDetailsService
它只有一个方法的声明,就是通过用户名去查找用户信息,从这里我们应该知道了,SpringSecurity回调UserDetails#loadUserByUsername去获取用户,但是它不知道用户信息存在哪里,所以定义成接口,让使用者去实现。在我们这个项目用 我们的用户是存在了数据库中,所以我们需要调用UserMapper的方法去访问数据库查询用户信息。这里我们新建一个类叫MyUserDetailsServiceImpl
package com.yukong.config; import com.yukong.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; 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; /** * @author yukong * @date 2019-04-11 17:35 */ @Service public class MyUserDetailServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userMapper.selectByUsername(username); } }
然后新建一个类去把我们的UserDetailsService配置进去
这里我们新建一个SecurityConfig
package com.yukong.config; 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.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author yukong * @date 2019-04-11 15:08 */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // 配置UserDetailsService 跟 PasswordEncoder 加密器 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); auth.eraseCredentials(false); } }
在这里我们还配置了一个PasswordEncoder加密我们的密码,大家都知道密码明文存数据库是很不安全的。
接下里我们插入一条记录,需要注意的是 密码需要加密。
package com.yukong.mapper; import com.yukong.SpringbootSpringsecurityApplicationTests; import com.yukong.entity.User; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import static org.junit.Assert.*; /** * @author yukong * @date 2019-04-11 16:53 */ public class UserMapperTest extends SpringbootSpringsecurityApplicationTests { @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserMapper userMapper; @Test public void insert() { User user = new User(); user.setUsername("yukong"); user.setPassword(passwordEncoder.encode("abc123")); userMapper.insertSelective(user); } }
接下来启动程序,并且登录,这次只需要输入插入到数据中的那条记录的用户名跟密码即可。
在这里一节中,我们了解到如何使用springsecurity 完成一个登录功能,接下我们将通过分析源码来了解为什么需要这个配置,以及SpringSecurity的工作原理是什么。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77554.html
摘要:开公众号差不多两年了,有不少原创教程,当原创越来越多时,大家搜索起来就很不方便,因此做了一个索引帮助大家快速找到需要的文章系列处理登录请求前后端分离一使用完美处理权限问题前后端分离二使用完美处理权限问题前后端分离三中密码加盐与中异常统一处理 开公众号差不多两年了,有不少原创教程,当原创越来越多时,大家搜索起来就很不方便,因此做了一个索引帮助大家快速找到需要的文章! Spring Boo...
摘要:里面配置的过滤器链当用户使用表单请求时进入返回一个的实例一般是从数据库中查询出来的实例然后直接到最后一个如果有错则抛错给前面一个进行抛错如果没有错则放行可以访问对应的资源上面是总的执行流程下面单独说一下的认证流程这个图应该都看得懂和里面的配 showImg(https://segmentfault.com/img/bVbvO0O?w=1258&h=261);web.xml里面配置的过滤...
摘要:创建一个工程在里面添加依赖,依赖不要随便改我改了出错了好几次都找不到原因可以轻松的将对象转换成对象和文档同样也可以将转换成对象和配置 1.创建一个web工程2.在pom里面添加依赖,依赖不要随便改,我改了出错了好几次都找不到原因 UTF-8 1.7 1.7 2.5.0 1.2 3.0-alpha-1 ...
摘要:通过上面我们知道对于表单登录的认证请求是交给了处理的,那么具体的认证流程如下从上图可知,继承于抽象类。中维护这一个对象列表,通过遍历判断并且最后选择对象来完成最后的认证。发布一个登录事件。 概要 前面一节,通过简单配置即可实现SpringSecurity表单认证功能,而今天这一节将通过阅读源码的形式来学习SpringSecurity是如何实现这些功能, 前方高能预警,本篇分析源码篇幅较...
阅读 2312·2021-11-17 09:33
阅读 842·2021-10-13 09:40
阅读 572·2019-08-30 15:54
阅读 777·2019-08-29 15:38
阅读 2416·2019-08-28 18:15
阅读 2474·2019-08-26 13:38
阅读 1840·2019-08-26 13:36
阅读 2128·2019-08-26 11:36