- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在上一篇博文《SpringBoot进阶教程(八十)Spring Security》中,已经介绍了在Spring Security中如何基于formLogin认证、基于HttpBasic认证和自定义用户名和密码。这篇文章,我们将介绍自定义登录界面的登录验证方式.
自定义认证的过程会用到Spring Security提供的UserDetail接口。源码如下:
自定义认证的过程还会用到Spring Security提供的UserDetailService接口,接口只有一个抽象方法loadUserByUsername,loadUserByUsername方法返回一个UserDetail对象,包含一些用于描述用户信息的方法,源码如下:
在项目中可以自定义UserDetails接口的实现类,直接使用Spring Security提供的UserDetails接口实现类org.springframework.security.core.userdetails.User也是可以的.
/** * @Author chen bo * @Date 2023/12 * @Des */ @Data @AllArgsConstructor @NoArgsConstructor public class UserLogin implements UserDetails { private String username; private String password; /** * 获取用户包含的权限,返回权限集合,权限是一个继承了GrantedAuthority的对象; * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities(){ return AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); } /** * 判断账户是否未过期,未过期返回true反之返回false * @return */ @Override public boolean isAccountNonExpired(){ return true; } /** * 判断账户是否未锁定 * @return */ @Override public boolean isAccountNonLocked(){ return true; } /** * 判断用户凭证是否没过期,即密码是否未过期 * @return */ @Override public boolean isCredentialsNonExpired(){ return true; } /** * 判断用户是否可用 * @return */ @Override public boolean isEnabled(){ return true; } }
我们先创建一个service层的方法,用户模拟获取获取。实际中一般从数据库或者redis中获取。这里为了简化,我们直接将用户信息写在内存中.
创建模拟DB的PO实体UserPo 。
/** * @Author chen bo * @Date 2023/12 * @Des */ @Data public class UserPo { private String userName; private String pwd; }
创建获取用户数据的service接口 。
/** * @Author chen bo * @Date 2023/12 * @Des */ public interface UserService { /** * 根据用户名获取用户信息 * @param userName * @return */ UserPo getUserByUserName(String userName); }
创建获取用户数据service接口的实现类.
/** * @Author chen bo * @Date 2023/12 * @Des */ @Service @Slf4j public class UserServiceImpl implements UserService { @Override public UserPo getUserByUserName(String userName){ List<UserPo> userPoList = userPoList(); if(CollectionUtils.isEmpty(userPoList)){ return null; } return userPoList.stream().filter(item -> userName.equals(item.getUserName())).findAny().orElse(null); } /** * 正常这一步应该是在DB或者redis中查询的,这里为了简化demo流程,直接在内存中写入固定用户集合 * @return */ private List<UserPo> userPoList(){ List<UserPo> userPoList = new ArrayList<>(); UserPo userPo = new UserPo(); userPo.setUserName("zhangsan"); userPo.setPwd("zs123456"); userPoList.add(userPo); userPo = new UserPo(); userPo.setUserName("lisi"); userPo.setPwd("ls123456"); userPoList.add(userPo); userPo = new UserPo(); userPo.setUserName("wangwu"); userPo.setPwd("ww123456"); userPoList.add(userPo); return userPoList; } }
下面我们来开始实现UserDetailService接口的loadUserByUsername方法。首先创建一个UserLogin(UserDetails接口的实现类)对象。接着创建UserDetailServiceImpl实现UserDetailService.
/** * @Author chen bo * @Date 2023/12 * @Des */ @Configuration @Slf4j public class UserDetailServiceImpl implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Autowired UserService userService; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { UserPo userPo = userService.getUserByUserName(userName); if(userPo == null){ throw new RuntimeException("用户名或密码错误"); } UserLogin user = new UserLogin(); user.setUsername(userPo.getUserName()); user.setPassword(passwordEncoder.encode(userPo.getPwd())); log.info("password : " + user.getPassword()); return user; } }
由于权限参数不能为空,所以这里先使用AuthorityUtils.commaSeparatedStringToAuthorityList方法模拟一个admin的权限,该方法可以将逗号分隔的字符串转换为权限集合.
此外我们还注入了PasswordEncoder对象,该对象用于密码加密,注入前需要手动配置。我们在BrowserSecurityConfig中配置它:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // ...... }
PasswordEncoder是一个密码加密接口,而BCryptPasswordEncoder是Spring Security提供的一个实现方法,我们也可以自己实现PasswordEncoder。不过Spring Security实现的BCryptPasswordEncoder已经足够强大,它对相同的密码进行加密后可以生成不同的结果.
这时候重启项目,访问http://localhost:9090/login,便可以使用user以及123456作为密码登录系统.
注意:BCryptPasswordEncoder对相同的密码生成的结果每次都是不一样的 。
默认的登录页面过于简陋,我们可以自己定义一个登录页面。为了方便起见,我们直接在src/main/resources/resources目录下定义一个login.html(不需要Controller跳转).
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> <link rel="stylesheet" href="css/login.css" type="text/css"> </head> <body> <form class="login-page" action="/login" method="post"> <div class="form"> <h3>请登录</h3> <input type="text" placeholder="请输入用户名" name="username" required="required" /> <br/> <input type="password" placeholder="请输入密码" name="password" required="required" /> <br/> <button type="submit">登录</button> </div> </form> </body> </html>
要怎么做才能让Spring Security跳转到我们自己定义的登录页面呢?很简单,只需要在BrowserSecurityConfig的configure中添加一些配置:
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登录 .loginPage("/login.html") // 登录跳转url .loginProcessingUrl("/login") // 处理表单登录url .and() .authorizeRequests() // 授权配置 .antMatchers("/login.html", "/css/**").permitAll() // 无需认证 .anyRequest() // 所有请求 .authenticated() // 都需要认证 .and().csrf().disable(); }
在未登录的情况下,当用户访问html资源的时候跳转到登录页,否则返回JSON格式数据,状态码为401。要实现这个功能我们将loginPage的URL改为/authentication/require,并且在antMatchers方法中加入该URL,让其免拦截
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登录 // .loginPage("/login.html") // 登录跳转url .loginPage("/authentication/require") .loginProcessingUrl("/login") // 处理表单登录url .and() .authorizeRequests() // 授权配置 .antMatchers("/login.html", "/css/**", "/authentication/require").permitAll() // 无需认证 .anyRequest() // 所有请求 .authenticated() // 都需要认证 .and().csrf().disable(); }
/** * @Author chen bo * @Date 2023/12 * @Des */ @RestController public class DemoController { private RequestCache requestCache = new HttpSessionRequestCache(); private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @GetMapping("/authentication/require") @ResponseStatus(HttpStatus.UNAUTHORIZED) public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException { SavedRequest savedRequest = requestCache.getRequest(request, response); if (savedRequest != null) { String url = savedRequest.getRedirectUrl(); // 为了方便测试,我们设置只有访问url中是以.html结尾时,才会跳转登录页,其它形式的全部返回提示语(访问资源需要身份认证)。具体业务中这里可以按需求设置。 if (StringUtils.endsWithIgnoreCase(url, ".html")) { redirectStrategy.sendRedirect(request, response, "/login.html"); } } return "访问资源需要身份认证"; } }
其中HttpSessionRequestCache为Spring Security提供的用于缓存请求的对象,通过调用它的getRequest方法可以获取到本次请求的HTTP信息。DefaultRedirectStrategy的sendRedirect为Spring Security提供的用于处理重定向的方法.
上面代码获取了引发跳转的请求,根据请求是否以.html为结尾来对应不同的处理方法。如果是以.html结尾,那么重定向到登录页面,否则返回”访问的资源需要身份认证!”信息,并且HTTP状态码为401(HttpStatus.UNAUTHORIZED).
为了方便测试,添加hello.html 。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 这是一个hello落地页 </body> </html>
1.访问http://localhost:8080/hello的时候页面便会跳转到http://localhost:8080/authentication/require,并且输出”访问的资源需要身份认证!” 。
2.访问http://localhost:8090/hello.html的时候,页面将会跳转到登录页面.
要改变默认的处理成功逻辑很简单,只需要实现org.springframework.security.web.authentication.AuthenticationSuccessHandler接口的onAuthenticationSuccess方法即可:
/** * @Author chen bo * @Date 2023/12 * @Des */ @Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private RequestCache requestCache = new HttpSessionRequestCache(); private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { // 默认打印出登陆信息 // httpServletResponse.setContentType("application/json;charset=utf-8"); // httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication)); // 跳转访问页面 // SavedRequest savedRequest = requestCache.getRequest(httpServletRequest, httpServletResponse); // redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, savedRequest.getRedirectUrl()); // 跳转制定页面 SavedRequest savedRequest = requestCache.getRequest(httpServletRequest, httpServletResponse); redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, "hello.html"); } }
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登录 .loginPage("/login.html") // 登录跳转url // .loginPage("/authentication/require") .loginProcessingUrl("/login") // 处理表单登录url .successHandler(authenticationSuccessHandler) .and() .authorizeRequests() // 授权配置 .antMatchers("/login.html", "/css/**", "/authentication/require").permitAll() // 无需认证 .anyRequest() // 所有请求 .authenticated() // 都需要认证 .and().csrf().disable(); }
与自定义登录成功处理逻辑类似,自定义登录失败处理逻辑需要实现org.springframework.security.web.authentication.AuthenticationFailureHandler的onAuthenticationFailure方法:
/** * @Author chen bo * @Date 2023/12 * @Des */ @Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); httpServletResponse.setContentType("application/json;charset=utf-8"); httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e.getMessage())); } }
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登录 .loginPage("/login.html") // 登录跳转url // .loginPage("/authentication/require") .loginProcessingUrl("/login") // 处理表单登录url .successHandler(authenticationSuccessHandler) .failureHandler(authenticationFailureHandler) .and() .authorizeRequests() // 授权配置 .antMatchers("/login.html", "/css/**", "/authentication/require").permitAll() // 无需认证 .anyRequest() // 所有请求 .authenticated() // 都需要认证 .and().csrf().disable(); }
其他参考/学习资料:
https://github.com/toutouge/javademosecond/tree/master/security-demo 。
作 者:请叫我头头哥 出 处:http://www.cnblogs.com/toutou/ 关于作者:专注于基础平台的项目开发。如有问题或建议,请多多赐教! 版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。 特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我 声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是作者坚持原创和持续写作的最大动力! 。
最后此篇关于SpringBoot进阶教程(八十一)SpringSecurity自定义认证的文章就讲到这里了,如果你想了解更多关于SpringBoot进阶教程(八十一)SpringSecurity自定义认证的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在使用 Tornado 与 twitter 等第三方进行身份验证。 我的登录处理程序看起来像这样 class AuthLoginHandler(BaseHandler, tornado.auth.
有没有一种真正的方法可以在 Pylons 中添加身份验证?我见过很多不同的方法,但大多数方法要么过时,要么过于复杂。是否有教程可以解释如何以良好而可靠的方式添加身份验证? 最佳答案 考虑使用 repo
RESTful 身份验证是什么意思,它是如何工作的?我在谷歌上找不到很好的概述。我唯一的理解是您在 URL 中传递了 session key (记住),但这可能是非常错误的。 最佳答案 如何在 RES
我正在考虑在基于插件的系统中实现安全性的多种方式。现在,当我说“安全”时,我的意思是: a) 插件系统的开发人员如何确保插件在核心平台上的使用是安全的。b) 插件开发人员如何确保在其平台上使用的插件是
我正在使用 WCF Webhttp 服务。我创建了一堆服务,剩下的就是放入用户身份验证... 问题 与其余架构风格保持一致,我是否应该针对用户 db 验证每个服务调用。 如果是这样,我应该在每次调用服
假设我想对 Mifare Classic 进行身份验证。 我如何知道要发送到卡的确切类型的 APDU? 例子。 这段代码: bcla = 0xFF; bins = 0x86; bp1 = 0x0;
我通过在文件 xyz.php 中编写以下代码登录到网站。当我运行这个文件时,我会登录到 moodle 网站。有什么方法可以像下面的登录代码一样注销吗? $user = authenticate_use
我有一个应用程序可以匿名访问除几个之外的所有 xpages。我需要强制用户登录这些 xpages。是使用 beforepageload 事件来检查用户登录页面并将其重定向到正确的方式还是有更好的方法?
我想用 ember.js 实现身份验证。 因此,当应用程序启动时,在路由器处理请求的 url 之前,我想检查用户状态。如果用户未通过身份验证,我想保存请求的 url 并重定向到特定的 url (/lo
您如何执行 jQuery Ajax 调用并在发送请求之前对调用进行身份验证? 我还没有登录所以必须进行身份验证。安全不是任何人都可以访问的问题,只需要进行身份验证。它只是基本的 http 身份验证,您
我尝试使用找到的 swift 代码 here在网站上找到here ,但响应是带有两个错误的 html 代码:“您必须输入密码!”和“您必须输入用户名!”我是 NSURLSession 的新手,并尝试更
我正在尝试连接到 Visa Direct API,但我没有通过基本的 SSL 证书认证,这是我的代码: import requests headers = { 'Content
我正在用 tornado 在 python 中开发一个 REST API,我将实现身份验证和授权,试图避免锁定到其他大项目,即 django。我也在通过论坛和 SO 环顾四周,我喜欢一个可能适合的解决
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭10
如何在 Android 中通过 HTTP 进行身份验证? 最佳答案 我非常难以在 Android 中通过 HTTP 进行身份验证,因为在浏览器(Web 和 Android native )中它工作完美
我有一些关于登录和 session 的问题。我有这段代码: 数据库查询: login: function(req,callback) { var query = 'SELECT id FROM
我开始使用 Swift 开发 iOS 应用。现在我正处于需要创建登录系统的部分。但是,我们需要人们提供的 LinkedIn 信息。 我如何在 iOS 中使用 OAuth2 API 来实现这一点? 我已
如果没有找到用户,问题出在每个 $routeChangeStart 上,如果我只输入 url,它仍然会引导我访问页面。 现在我已经在服务器上重写了规则。 Options +FollowSymlinks
简单代码 require 'net/http' url = URI.parse('get json/other data here [link]') req = Net::HTTP::Get.new(
参考文档: https://docs.sonarqube.org/latest/instance-administration/security/ 概述 SonarQube具有
我是一名优秀的程序员,十分优秀!