SpringSecurity给我们提供了一套最基本的认证方式,可是这种方式远远不能满足大多数系统的需求。不过好在SpringSecurity给我们预留了许多可扩展的接口给我们,我们可以基于这些接口实现自己的认证方式。
一、前期准备工作
1.1、创建示例数据库
Student表:
create table student ( id int auto_increment primary key, stuName varchar(8) null, password varchar(16) null, joinTime datetime null, clz_id int null ) ;
Classes(班级)表:
create table classes ( id int auto_increment primary key, clz_name varchar(16) not null ) ;
1.2、添加相关依赖
compile group: 'mysql', name: 'mysql-connector-java' compile group: 'org.springframework.security', name: 'spring-security-taglibs' compile('org.springframework.boot:spring-boot-starter-jdbc')
二、实现步骤
2.1 定义Student类
package com.bdqn.lyrk.security.study.app.pojo; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.sql.Timestamp; import java.util.Collection; public class Student extends User { private Timestamp joinTime; public Timestamp getJoinTime() { return joinTime; } public void setJoinTime(Timestamp joinTime) { this.joinTime = joinTime; } public Student(String username, String password, Collection extends GrantedAuthority> authorities) { super(username, password, authorities); } }
在这里定义的类继承User,User是SpringSecurity里的一个类,用以描述一个用户和其最基本的属性,当然我们要扩展它的用户我们也可以实现UserDetails接口
2.2 实现UserDetailsService
package com.bdqn.lyrk.security.study.app.service; import com.bdqn.lyrk.security.study.app.pojo.Student; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; 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; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Map; @Service public class UserService implements UserDetailsService { @Autowired private JdbcTemplate jdbcTemplate; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User.UserBuilder users = User.withDefaultPasswordEncoder(); Mapmap = jdbcTemplate.queryForMap("select t.clz_name,t1.stuName,t1.password,t1.joinTime from student t1 inner join classes t on t.id = t1.clz_id where stuName = ?", username); Timestamp joinTime = null; if (map != null && map.size() > 0) { String stuName = (String) map.get("stuName"); String password = (String) map.get("password"); joinTime = (Timestamp) map.get("joinTime"); String clzName = (String) map.get("clz_name"); users.password(password); users.username(stuName); SimpleGrantedAuthority authority = new SimpleGrantedAuthority(clzName); List list = new ArrayList<>(); list.add(authority); users.authorities(list); } UserDetails userDetails = users.build(); Student student = new Student(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities()); // UserDetails userDetails = User.withDefaultPasswordEncoder(). student.setJoinTime(joinTime); return student; } }
在这个接口里我们要实现根据用户名查找用户的方法,那么一般情况下我们都会根据自己系统的用户表来获取用户信息,这里面注意几个方面:
1)需要设置PasswordEncoder
2) 需要设置其角色信息,那么在这里我用班级来表示用户的角色
3)用户的三个重要属性就是 用户名,密码与权限
4) 这里的返回值(UserDetails)不能返回null,如果根据用户名找不到对应的用户可以抛出UsernameNotFoundException异常
2.3 改造WebSecurityConfig
package com.bdqn.lyrk.security.study.app.config; import com.bdqn.lyrk.security.study.app.service.UserService; import org.springframework.beans.factory.annotation.Autowired; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * spring-security的相关配置 * * @author chen.nie * @date 2018/6/7 **/ @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; @Override protected void configure(HttpSecurity http) throws Exception { /* 1.配置静态资源不进行授权验证 2.登录地址及跳转过后的成功页不需要验证 3.其余均进行授权验证 */ http. authorizeRequests().antMatchers("/static/**").permitAll(). and().authorizeRequests().antMatchers("/user/**").hasRole("7022"). and().authorizeRequests().anyRequest().authenticated(). and().formLogin().loginPage("/login").successForwardUrl("/toIndex").permitAll() .and().logout().logoutUrl("/logout").invalidateHttpSession(true).deleteCookies().permitAll() ; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //设置自定义userService auth.userDetailsService(userService); } }
在这里主要做以下处理:
1)针对于/user/**路径的请求需要设置对应的权限
2) 做用户注销的处理,用户注销时需要销毁session与cookie
3)配置自定义UserDetailService
2.4、改造index.jsp
<%-- Created by IntelliJ IDEA. User: chen.nie Date: 2018/6/8 Time: 上午9:56 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <html> <head> <title>Titletitle> head> <body> 欢迎:${user.username} <sec:authorize access="hasRole('7022')"> 加入时间:${user.joinTime} sec:authorize> <form action="/logout" method="post"> <input type="submit" value="退出" /> <sec:csrfInput/> form> body> html>
在这里面我们使用spring对security标签的支持判断当前用户是否有对应的角色,另外我们在处理登出操作时必须为post提交且有对应的token防止csrf