Authority    权限
Credential    证书
Grant    授予

Authentication 身份验证

以下,我们将通过四步,逐步实现spring-security的username+password身份验证的登录功能。

一、添加spring-boot-start-security依赖即可实现默认的username+password登录。(默认用户认证)
二、实现自定义的固定用户和密码(内存用户认证)
三、实现自定义用户及密码登录的系统(UserDetailsService认证)
四、配置自定义页面,可配置的相关页面包括:登录表单页,登录错误页,登录成功页等。

请注意,我们是以一个spring-boot-starter-web项目为起点。

一、添加spring-boot-start-security依赖即可实现默认的username+password登录。(默认用户认证)

  1. <!-- 依赖:spring-security -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-security</artifactId>
  5. </dependency>

启动web应用,访问站点资源时,会出现spring-security提供的默认登录页面

其默认用户名为:user

登录密码在启动信息中可以找到:

填写正确即可登录成功。

这个最简单的配置适用于只有一个用户且每次系统启动后查阅更新密码的系统。当然,这种系统不常见。

二、实现自定义的固定用户和密码(内存用户认证)

需添加自定义配置,我们以java配置方式实现。

1. 创建一个继承自org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter的类;

2. 类上以@Configuration和@EnableWebSecurity注解,表明这是一个java配置类,并启用spring-security身份认证;

3. 覆写configure(AuthenticationManagerBuilder auth)方法;

4. 调用auth对象的.inMemoryAuthentication().withUser("xxx").password("xxx").roles("USER")等方法,指定用户、密码及角色,多个用户可以调用.and()方法来连接.withUser方法。

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter{
  4.  
  5. @Override
  6. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  7. auth
  8. .inMemoryAuthentication() // 内存用户认证
  9.   .withUser("xxx").password("xxx").roles("USER") // 配置用户xxx密码xxx及角色USER
  10.   .and()
           .withUser("yyy").password("yyy").roles("USER") // 配置用户yyy密码yyy及角色USER
  1. ; } }

重启web应用后,默认的user用户登录方式已失效,

现在可以用户xxx或yyy登录(针对spring4版本)

使用spring5版本的话,在此,会报错:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null",登录页面无法跳转

spring5要求必须指定密码编译码器,我们可以用BCryptPasswordEncoder。

修改一下configure(AuthenticationManagerBuilder auth)方法,填加一行代码:.passwordEncoder(new BCryptPasswordEncoder())    // 指定加密方式

  1. @Override
  2. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  3. auth
  4. .inMemoryAuthentication() // 内存用户认证
  5. .passwordEncoder(new BCryptPasswordEncoder()) // 指定加密方式
  6. .withUser("xxx").password("xxx").roles("USER") // 配置用户xxx密码xxx及角色USER
        .and()
         .withUser("yyy").password("yyy").roles("USER") // 配置用户yyy密码yyy及角色USER
        ;
      }

重新登录,还出错:

控制台有提示:Encoded password does not look like BCrypt(看上去不像BCrypt编码的密码)

我们的密码"xxx"是以明文方式传递的,用new BCryptPasswordEncoder().encode("xxx")改为密文即可。

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter{
  4.  
  5. @Override
  6. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  7. auth
  8. .inMemoryAuthentication() // 内存用户认证
  9. .passwordEncoder(new BCryptPasswordEncoder()) // 指定加密方式
  10. .withUser("xxx").password(new BCryptPasswordEncoder().encode("xxx")).roles("USER") // 配置用户xxx密码xxx及角色USER
  11. .and()
  12. .withUser("yyy").password(new BCryptPasswordEncoder().encode("yyy")).roles("USER") // 配置用户yyy密码yyy及角色USER
  13. ;
  14. }
  15.  
  16. }

以上是修改后的配置类,再次重启登录就正常了。

这个简单的配置适用于拥有少数明确固定用户且密码不得改变的系统。当然,这种系统不够灵活。

三、实现自定义用户及密码登录的系统(UserDetailsService认证)

1. 依然使用上面的配置类;

2. 只是调用auth的.userDetailsService方法,该方法需要一个UserDetailsService接口作为参数;

3. 需要实现UserDetailsService接口的loadUserByUsername(String username):UserDetails方法来完成用户身份认证;

  loadUserByUsername(String username)返回一个UserDetails接口;

  UserDetails接口要求提供用户名、密码、角色等属性信息;

4. 注意指定密码编译器,可参考前例。

我们用一个私有方法来提供UserDetails接口作为示例,实际运用时推荐调用一个实体服务方法(例如:UserService.findBy(String username):User)来提供UserDetails接口。示例代码如下:

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter{
  4.  
  5. @Override
  6. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  7. auth.userDetailsService(new UserDetailsService(){
  8.  
  9. @Override
  10. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  11. return findBy(username); //findBy(username)仅是一个示例方法
  12. //return UserService.findByName(username); //通常,应该用一个Serice来实现
  13. }
  14.  
  15. // 示例方法findBy(username)
  16. private UserDetails findBy(String username) {
  17. return new UserDetails(){
  18.  
  19. private String username = "aaa"; //假定用户名为aaa
  20. private String password = "aaa"; //假定用户密码为aaa
  21.  
  22. @Override
  23. public String getUsername() {
  24. return username;
  25. }
  26.  
  27. @Override
  28. public String getPassword() {
  29. // 注意在此返回指定密码编译器编译的密文密码
  30. return new BCryptPasswordEncoder().encode(password);
  31. }
  32.  
  33. //以上属性通常是自定义实体类User定义的
  34. //以下属性是User实现UserDetails接口必须的
  35.  
  36. @Override
  37. public Collection<? extends GrantedAuthority> getAuthorities() {
  38. return java.util.Arrays.asList(new SimpleGrantedAuthority("USER"));//默认为USER角色
  39. }
  40.  
  41. @Override
  42. public boolean isAccountNonExpired() {
  43. return true; //默认账户未过期
  44. }
  45.  
  46. @Override
  47. public boolean isAccountNonLocked() {
  48. return true; //默认用户未被锁定
  49. }
  50.  
  51. @Override
  52. public boolean isCredentialsNonExpired() {
  53. return true; //默认证书未过期
  54. }
  55.  
  56. @Override
  57. public boolean isEnabled() {
  58. return true; //默认有效,即用户未被停用
  59. }};
  60. }})
  61. .passwordEncoder(new BCryptPasswordEncoder()) // 指定密码编译器
  62. ;
  63. }
  64.  
  65. }

配置类经过以上修改,再次重启,以用户aaa密码aaa就正常登录了。

需注意事项:

1. Arrays来自java.util.Arrays;

2. spring5中必须指定密码编译器,.passwordEncoder(new BCryptPasswordEncoder())

3. new SimpleGrantedAuthority("USER")只是示例性的简单授权,实际应用中应以数据源来提供用户角色。

4. 如果我们另以MyUserDetailsService实现UserDetailsService接口的话,代码更清晰,实际上主体代码是:

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter{
  4.  
  5. @Override
  6. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  7. auth
          .userDetailsService(new MyUserDetailsService())
          .passwordEncoder(new BCryptPasswordEncoder()) // 指定密码编译器
  8. ;
  9. }
  10. }

以上为UserDetailsService认证的java配置类主体代码。

这种通过实现UserDetailsService接口来完成用户身份认证的方式基本可以满足绝大部分系统需求,即username+password认证方式。

至此,功能倒是实现了,但spring-security提供的默认登录页面,未免过于简陋,这些界面是可以定制的。

四、配置自定义页面,可配置的相关页面包括:登录表单页,登录错误页,登录成功页等。

依然使用上面的配置类,重写configure(HttpSecurity http)方法来配置自定义页面。

以登录页为例,我们以spring-boot-starter-web和Thymeleaf来做示例。

前例中使用spring-security默认的登录页url为:http://localhost:8080/login,登录错误页url为:http://localhost:8080/login?error=true。

1. 对应的,我们设计自定义的url:登录http://localhost:8080/uia/login,登录错误http://localhost:8080/uia/login?error=true来取代。

2. 定义一个控制器来响应上面的登录请求

3. 编写登录页面,默认的输入参数name分别为username和password,自定义的话可以用类似.usernameParameter("usr").passwordParameter("pwd")方法来指定;

4. 修改配置类,重写configure(HttpSecurity http)方法,若啥也不写的话,默认配置为匿名访问所有资源。

5. 配置开放静态资源(/res/**)及及登录等(/uia/**),而其他请求都得认证;

6. 配置登录表单等请求;

7. 为简化复杂性,忽略csft防范!!! 切记,生产环境中不可忽略。spring4+默认启用csft,会拦截所有的POST请求,表现为提交登录表单无效。

  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3. http
  4. .authorizeRequests()
  5. .antMatchers("/css/**", "/uia/**").permitAll() //开放静态资源和登录等页面
  6. .anyRequest().authenticated() //其他所有资源均需认证
  7. .and()
  8. .formLogin() // 启用登录表单
  9. .loginPage("/uia/login") //指定登录请求
  10. .failureUrl("/uia/login?error=true") //指定登录失败请求
  11. //.usernameParameter("usr").passwordParameter("pwd") //指定form中输入域input的name属性
  12. .and().csrf().disable() //示例中为简化复杂性,忽略csft防范!!!切记,此项不可用于生产环境。
  13. ;
  14. }

Controller简单示例,其他如注册,找回密码等代码已移除。

  1. @RestController
  2. @RequestMapping("/uia")
  3. public class UiaController {
  4.  
  5. @RequestMapping(value="/login")
  6. public ModelAndView login(){
  7. //TODO 处理些页面后台数据,例如:登录提示、错误原因等
  8. return new ModelAndView("loginView");
  9. }
  10.  
  11. @RequestMapping("/logout")
  12. public ModelAndView logout(HttpServletRequest request, HttpServletResponse response){
  13. Authentication auth = SecurityContextHolder.getContext().getAuthentication();
  14. if (auth != null){
  15. new SecurityContextLogoutHandler().logout(request, response, auth);
  16. }
  17. return new ModelAndView("loginView");
  18. }
  19. }

登录页loginView.html简单示例,在此使用了Thymeleaf模板,多余的如bootstrap代码均已移除。

  1. !DOCTYPE html>
  2. <html xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Login</title>
  6. </head>
  7. <body>
  8. <h2>Please login</h2>
  9. <form name="f" method="post" th:action="@{/uia/login}" >
  10. <input type="text" name="username" placeholder="user name" />
  11. <input type="password" name="password" placeholder="password" />
  12. <input type="submit" value="login" />
  13. </form>
  14. <p><span th:if="${param.error}">登录失败</span></p>
  15. </body>
  16. </html>

注意:

form的action属性被th:action="@{/uia/login}所替代,这样,在post提交时,会自动补上sessionId。

最后,贴上完成的SecurityConfig.java代码

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfigB extends WebSecurityConfigurerAdapter{
  4.  
  5. @Override
  6. protected void configure(HttpSecurity http) throws Exception {
  7. http
  8. .authorizeRequests()
  9. .antMatchers("/css/**", "/uia/**").permitAll() //开放静态资源和登录等页面
  10. .anyRequest().authenticated() //其他所有资源均需认证
  11. .and()
  12. .formLogin() // 启用登录表单
  13. .loginPage("/uia/login") //指定登录请求
  14. .failureUrl("/uia/login?error=true") //指定登录失败请求
  15. //.usernameParameter("usr").passwordParameter("pwd") //指定form中输入域input的name属性
  16. .and().csrf().disable() //示例中为简化复杂性,忽略csft防范!!!切记,此项不可用于生产环境。
  17. ;
  18. }
  19.  
  20. @Override
  21. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  22. auth.userDetailsService(new UserDetailsService(){
  23.  
  24. @Override
  25. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  26. return findBy(username); //findBy(username)仅是一个示例方法
  27. //return UserService.findByName(username); //通常,应该用一个Serice来实现
  28. }
  29.  
  30. // 示例方法findBy(username)
  31. private UserDetails findBy(String username) {
  32. return new UserDetails(){
  33.  
  34. private String username = "aaa"; //假定用户名为aaa
  35. private String password = "aaa"; //假定用户密码为aaa
  36.  
  37. @Override
  38. public String getUsername() {
  39. return username;
  40. }
  41.  
  42. @Override
  43. public String getPassword() {
  44. // 注意在此返回指定密码编译器编译的密文密码
  45. return new BCryptPasswordEncoder().encode(password);
  46. }
  47.  
  48. //以上属性通常是自定义实体类User定义的
  49. //以下属性是User实现UserDetails接口必须的
  50.  
  51. @Override
  52. public Collection<? extends GrantedAuthority> getAuthorities() {
  53. return java.util.Arrays.asList(new SimpleGrantedAuthority("USER"));
  54. }
  55.  
  56. @Override
  57. public boolean isAccountNonExpired() {
  58. return true; //默认账户未过期
  59. }
  60.  
  61. @Override
  62. public boolean isAccountNonLocked() {
  63. return true; //默认用户未被锁定
  64. }
  65.  
  66. @Override
  67. public boolean isCredentialsNonExpired() {
  68. return true; //默认证书未过期
  69. }
  70.  
  71. @Override
  72. public boolean isEnabled() {
  73. return true; //默认有效,即用户未被停用
  74. }};
  75. }})
  76. .passwordEncoder(new BCryptPasswordEncoder()) // 指定密码编译器
  77. ;
  78. }
  79. }

请自行实现UserService服务,启用下面第27行代码:

把配置文件变成这样

  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfigC extends WebSecurityConfigurerAdapter{
  4.  
  5. @Autowired
  6. UserService userService;
  7.  
  8. @Override
  9. protected void configure(HttpSecurity http) throws Exception {
  10. http
  11. .authorizeRequests()
  12. .antMatchers("/res/**", "/uia/**").permitAll() //开放静态资源和登录等页面
  13. .anyRequest().authenticated() //其他所有资源均需认证
  14. .and()
  15. .formLogin() // 启用登录表单
  16. .loginPage("/uia/login") //指定登录请求
  17. .failureUrl("/uia/login?error=true") //指定登录失败请求
  18. //.usernameParameter("usr").passwordParameter("pwd") //指定form中输入域input的name属性
  19. //.and().csrf().disable() //示例中为简化复杂性,忽略csft防范!!!切记,此项不可用于生产环境。
  20. ;
  21. }
  22.  
  23. @Override
  24. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  25. auth.userDetailsService(new UserDetailsService(){
  26.  
  27. @Override
  28. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  29. return userService.findByName(username); //通常,应该用一个Service来实现
  30. }
  31.  
  32. })
  33. .passwordEncoder(new BCryptPasswordEncoder()) // 指定密码编译器
  34. ;
  35. }
  36. }

至此,spring-security也算可以用起来了。

springboot security的更多相关文章

  1. Springboot security cas整合方案-实践篇

    承接前文Springboot security cas整合方案-原理篇,请在理解原理的情况下再查看实践篇 maven环境 <dependency> <groupId>org.s ...

  2. Springboot security cas源码陶冶-ExceptionTranslationFilter

    拦截关键的两个异常,对异常进行处理.主要应用异常则跳转至cas服务端登录页面 ExceptionTranslationFilter#doFilter-逻辑入口 具体操作逻辑如下 public void ...

  3. Springboot security cas源码陶冶-CasAuthenticationFilter

    Springboot security cas整合方案中不可或缺的校验Filter类或者称为认证Filter类,其内部包含校验器.权限获取等,特开辟新地啃啃 继承结构 - AbstractAuthen ...

  4. Springboot security cas整合方案-原理篇

    前言:网络中关于Spring security整合cas的方案有很多例,对于Springboot security整合cas方案则比较少,且有些仿制下来运行也有些错误,所以博主在此篇详细的分析cas原 ...

  5. SpringBoot security关闭验证

    SpringBoot security关闭验证 springboot2.x security关闭验证https://www.cnblogs.com/guanxiaohe/p/11738057.html ...

  6. springboot+security整合(3)自定义鉴权

    说明 springboot 版本 2.0.3源码地址:点击跳转 系列 springboot+security 整合(1) springboot+security 整合(2) springboot+se ...

  7. springboot+security整合(2)自定义校验

    说明 springboot 版本 2.0.3源码地址:点击跳转 系列 springboot+security 整合(1) springboot+security 整合(2) springboot+se ...

  8. springboot+security整合(1)

    说明 springboot 版本 2.0.3源码地址:点击跳转 系列 springboot+security 整合(1) springboot+security 整合(2) springboot+se ...

  9. springboot security 安全

    spring security几个概念 “认证”(Authentication) 是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统) . “授权 ...

  10. SpringBoot + Security实现权限控制

    网上找了好几个,因为各种原因不太行,下面这个亲测可行 参考:https://blog.csdn.net/u012702547/article/details/54319508 基于SpringBoot ...

随机推荐

  1. Mac OS X L2TP Client Setup

    原文链接:http://www.softether.org/4-docs/2-howto/9.L2TPIPsec_Setup_Guide_for_SoftEther_VPN_Server/5.Mac_ ...

  2. C语言练习题库----数组

    有如下语句 int a[10] = {1,2,3,4,5,6,7,8,9,10};int *p = a;则数值为9的表达式是______ *p+9                       b)   ...

  3. Java 内部类的作用

    1.内部类可以很好的实现隐藏 一般的非内部类,是不允许有 private 与protected权限的,但内部类可以 2.内部类拥有外围类的所有元素的访问权限 3.可是实现多重继承 4.可以避免修改接口 ...

  4. E4A 与JS交互事件无反应

    今天碰到一个问题,E4A与JS的交互,调用JS函数后,事件没有任何反应,给JS赋值,会看到浏览框中有内容显示,但是事件为什么就没反应呢. 把官方的例程打开编译试了也不行. 后来在群中问了,原来是这里设 ...

  5. AttributePriority

    还有AttributePriority,我们可以设置编译时优先级.如果我们对目标标记了多个aspect,这样postsharp就不确定注入先后顺序,这样不能确保正确性,在vs编译时候我们会看见警告:T ...

  6. ubuntu设置 SSH 通过密钥对登录

    1. 制作密钥对 首先在服务器上制作密钥对.登录到打算使用密钥登录的账户,然后执行以下命令: [root@host ~]$ ssh-keygen <== 建立密钥对 Generating pub ...

  7. 【linux】之日志查看

    搜索日志 -n 显示行号 grep 1570xxxx -n callback.tomcat-catalina-out 显示从第多少行~多少行 sed -n '464913,465020p' callb ...

  8. LeetCode——17. Letter Combinations of a Phone Number

    一.题目链接: https://leetcode.com/problems/letter-combinations-of-a-phone-number/ 二.题目大意: 给定一段数字字符串,其中每个数 ...

  9. note10 元组

    元组 Tuple +元组即不可变(immutable)列表 除了可改变列表内容的方法外,其他方法均适用于元组 因此,索引.切片.len().print等均可用 但是,appeng.extend.del ...

  10. 用户层APC队列使用

    一 参考 https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-que ...