在如今的项目开发中,登录和权限管理是一个项目最基本的需求,对于这个需求SpringSecurity一个很好的架构,这一系列博文是我自己学习SpringSecurity的一些总结和自己踩过的坑和从零开始搭接一个完整的基于SpringSecurity的登录(包括第三方的登录)和权限管理的项目。
首先让我们来搭接一个springboot的项目,这里就不在多说,不会的可以自行百度,或者直接上gitHub上拉取我的这个范例git地址,首先我们来写一个controller用来测试使用:
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping
public List<User> query() {
List<User> users = new ArrayList<User>();
users.add(new User());
users.add(new User());
return users;
}
}
使用SpringSecurity首先导入我们需要的jar包:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId>
</dependency>
其实这样已经完成了一个springsecurity来管理我们的项目,当我们在浏览器上访问/user时,就会弹出SpringSecurity默认的表单登录页面,如图:
但是这个样子肯定不能满足我们的需求,所以我们需要对SpringSecurity进行一些配置,配置代码如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//表示使用form表单提交
.loginPage("/login.html")//我们定义的登录页
.loginProcessingUrl("/authentication/form")//因为SpringSecurity默认是login请求为登录请求,所以需要配置自己的请求路径
.and()
.authorizeRequests()//对请求进行授权
.antMatchers("/login.html").permitAll()//表示login.html路径不会被拦截
.anyRequest()//表示所有请求
.authenticated()//需要权限认证
.and()
.csrf().disable();//这是SpringSecurity的安全控制,我们这里先关掉
}
}
然后就是我们简单的表单提交登录页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
当我们第一次访问/user时,就会跳到我们自定义的登录页面了
然后就需要对我们的登录的用户名和密码进行判断,先上代码:
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
String username = s;
System.out.println("用户名为"+username);
//根据username去数据库里查询获得密码
String password = passwordEncoder.encode("123456");
System.out.println("数据库密码为:"+password);
return new User(username, passwordEncoder.encode("123456"),
true,true,true,true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
其中:PasswordEncoder是SpringSecurity提供给我们进行加密解密的,这里返回的User类不是我们自定义的,也是SpringSecurity提供好的,如果我们需要自定的话,可以让我们的类去集成UserDetails这个接口;看一下我们使用的这个User类的源码:
public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
if (username != null && !"".equals(username) && password != null) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
} else {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
}
这个User类有4个判断,我们可以根据自己的设计进行逻辑判断是否满足条件。如果在启动项目的时候报PasswordEncoder 无法找到时,这个可以看一下我的另一篇博客,里面有解决方法解决PasswordEncoder无法注入问题。然后再次运行我们的项目:
再次登录,就可以登录成功,就可以进入我们的Controller层:
当我们重复登录两次时,看一下后台的输出的内容:
用户名为hudong
数据库密码为:$2a$10$laAqthwHpbTDMDxGkdPe9.zj.ARiAmhr7Lo0GDWXORPSdfafuPkwK
用户名为hudong
数据库密码为:$2a$10$fvmpjcWs9qDpFE7tgfK.O.FJCOky.H4EadfdXSOPmSPhT03YSCGpC
我们两次输入的密码是相同的,但后台打印出来的密文是不一样的,这就是PasswordEncoder的强大的地方,他通过加盐机制,可以让同一个密码返回不同的结果,有兴趣的小伙伴可以给我留言,人多的话,花时间给大家总结一下,这里就不详说了。
因为现在好多项目都是前后端分离的项目,我们只需要将登陆的成功后的数据发送给前端,而不需要我们来进行页面的跳转,那么如果来处理登录成功和失败以后的操作呢?
@Component("demoAuthenticationFailureHandler")
public class DemoAuthenticationFailureHandler implements AuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(DemoAuthenticationFailureHandler.class);
@Autowired
private ObjectMapper objectMapper;
/**
* 处理登录失败的请求
* @author hdd
* @date 2018/12/10 0010 10:15
* @param [httpServletRequest, httpServletResponse, e-用来封装错误信息对象的]
* @return void
*/
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
logger.info("登录失败");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e));
}
}
@Component("demoAuthenticationSuccessHandler")
public class DemoAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(DemoAuthenticationSuccessHandler.class);
@Autowired
private ObjectMapper objectMapper;
/**
* 处理登录成功的请求
* @author hdd
* @date 2018/12/10 0010 10:16
* @param [httpServletRequest, httpServletResponse, authentication—封装登录信息的]
* @return void
*/
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication)
throws IOException, ServletException {
logger.info("登录成功");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
然后在我们的配合文件里,加入我们新的配置:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DemoAuthenticationSuccessHandler demoAuthenticationSuccessHandler;
@Autowired
private DemoAuthenticationFailureHandler demoAuthenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//表示使用form表单提交
.loginPage("/login.html")//我们定义的登录页
.loginProcessingUrl("/authentication/form")//因为SpringSecurity默认是login请求为登录请求,所以需要配置自己的请求路径
.successHandler(demoAuthenticationSuccessHandler)//登录成功的操作
.failureHandler(demoAuthenticationFailureHandler)//登录失败的操作
.and()
.authorizeRequests()//对请求进行授权
.antMatchers("/login.html").permitAll()//表示login.html路径不会被拦截
.anyRequest()//表示所有请求
.authenticated()//需要权限认证
.and()
.csrf().disable();//这是SpringSecurity的安全控制,我们这里先关掉
}
}
当我们重新进行登录的时候,看给我们返回的信息:
成功的时候:
失败的时候: