spring security是一个安全登录框架,常用于用户登录、注销、角色权限控制、页面接口访问控制等等,功能强大且上手较难,最近搭建基础开发框架简单集成一下。目前大多数教程都比较老旧,仍然使用了模板引擎、jsp相关的技术,这不符合我们目前的日常开发现状。这篇博客后端使用springboot2.x+mybatis,前端只使用了2个简单的html页面,比较易于理解security。
https://github.com/hellozhaoxudong/springbootMerge
/**
* @ClassName SysUser
* @description 用户信息实体
* @author [email protected]
* @date 2019/4/28 15:50
* @version 1.0
* @since JDK 1.8
*/
@Table(name = "sys_user")
public class SysUser {
/** 用户ID **/
@Id
private Long userId;
/** 登录名 **/
@Column
private String username;
/** 密码 **/
@Column
private String password;
/** 用户真实姓名 **/
@Column
private String userRealName;
/** 手机号 **/
@Column
private String phone;
/** 邮箱 **/
@Column
private String email;
/** 信用值 **/
@Column
private Long credit;
/** getter & setter **/
}
/**
* @ClassName SysRole
* @description 角色信息实体
* @author [email protected]
* @date 2019/4/28 15:57
* @version 1.0
* @since JDK 1.8
*/
@Table(name = "sys_role")
public class SysRole {
/** 角色ID **/
@Id
private Long roleId;
/** 角色编码 **/
@Column
private String roleCode;
/** 角色名称 **/
@Column
private String roleName;
/** setter & getter **/
}
/**
* @ClassName SysUserRole
* @description 用户角色实体
* @author [email protected]
* @date 2019/4/28 16:00
* @version 1.0
* @since JDK 1.8
*/
@Table(name = "sys_user_role")
public class SysUserRole {
/** 用户角色信息ID **/
@Id
private Long userRoleId;
/** 用户ID **/
@Column
private Long userId;
/** 角色ID **/
@Column
private Long roleId;
/** 角色编码 **/
@Transient
private String roleCode;
/** 角色名称 **/
@Transient
private String roleName;
/** setter & getter **/
}
/**
* @ClassName SysUserService
* @description 用户信息逻辑层
* @author [email protected]
* @date 2019/4/28 16:14
* @version 1.0
* @since JDK 1.8
*/
@Service
public class SysUserService {
@Autowired
private SysUserMapper mapper;
/**
* loadUserByUsername : 根据用户名查询用户信息
*
* @author [email protected]
* @version 1.0
* @date 2019/4/28 16:21
* @return java.util.List
* @since JDK 1.8
*/
public SysUser loadUserByUsername(String username){
SysUser queryUser = new SysUser();
queryUser.setUsername(username);
List sysUserList = mapper.select(queryUser);
if(null == sysUserList || sysUserList.size() == 0){
throw new BaseException("用户名不存在!");
}else if(sysUserList.size() > 1){
throw new BaseException("用户名存在冲突!");
}
return sysUserList.get(0);
}
}
/**
* @ClassName SysUserMapper
* @description 用户信息Mapper
* @author [email protected]
* @date 2019/4/28 16:14
* @version 1.0
* @since JDK 1.8
*/
public interface SysUserMapper extends Mapper, InsertListMapper {
}
/**
* @ClassName SysUserRoleService
* @description 用户角色关联信息逻辑处理层
* @author [email protected]
* @date 2019/4/28 16:38
* @version 1.0
* @since JDK 1.8
*/
@Service
public class SysUserRoleService {
@Autowired
private SysUserRoleMapper mapper;
/**
* querySysUserRoleByUserId : 根据用户id查询角色信息
*
* @author [email protected]
* @version 1.0
* @date 2019/4/28 16:56
* @return java.util.List
* @since JDK 1.8
*/
public List querySysUserRoleByUserId(Long userId){
List list = mapper.querySysUserRoleByUserId(userId);
if(null == list || list.size() < 1){
throw new BaseException("用户下无任何角色!请联系开发者");
}
return list;
}
}
/**
* @ClassName SysUserRoleMapper
* @description 用户角色信息Mapper
* @author [email protected]
* @date 2019/4/28 16:36
* @version 1.0
* @since JDK 1.8
*/
public interface SysUserRoleMapper extends Mapper, InsertListMapper {
/**
* querySysUserRoleByUserId : 根据用户id查询角色信息
*
* @author [email protected]
* @version 1.0
* @date 2019/4/28 16:51
* @return java.util.List
* @since JDK 1.8
*/
List querySysUserRoleByUserId(@Param("userId") Long userId);
}
org.springframework.boot
spring-boot-starter-security
自己写一个类实现UserDetailsService,重写loadUserByUsername方法,作用就是我们自己去组装登录用户的用户名、密码、角色集信息并在配置类中注入给security。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserRoleService sysUserRoleService;
/**
* loadUserByUsername : 获取用户信息(用户名,密码,角色集)
*
* 返回UserDetails,这是一个接口,通常返回它的字类org.springframework.security.core.userdetails.User
* User的构造需要三个参数:用户名,密码,角色集
*
* @author [email protected]
* @version 1.0
* @date 2019/4/29 14:36
* @return org.springframework.security.core.userdetails.UserDetails
* @since JDK 1.8
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 用户信息
SysUser sysUser = sysUserService.loadUserByUsername(username);
// 角色信息
List sysUserRoleList = sysUserRoleService.querySysUserRoleByUserId(sysUser.getUserId());
List authorities = new ArrayList();
SimpleGrantedAuthority simpleGrantedAuthority;
for(SysUserRole sysUserRole : sysUserRoleList){
simpleGrantedAuthority = new SimpleGrantedAuthority(sysUserRole.getRoleCode());
authorities.add(simpleGrantedAuthority);
}
return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
}
}
@Configuration // 标识该类为配置类
@EnableWebSecurity // 开启Spring Security服务
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启全局Spring Security注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
// 指定用户,角色信息加载及密码加密方式,将默认的userDetailsService替换成我们自己实现的UserDetailsServiceImpl
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置拦截规则
http.authorizeRequests()
.antMatchers("/login.html","/api/login", "/visitor/**").permitAll() // "/login.html"是登录页面,"/api/login"是登录url, "/visitor/**"游客访问相关的页面和接口所以我们直接不拦截
.antMatchers("/admin/**").hasAnyRole("ADMIN") // "/admin/ \*\*"相关的页面和接口,只能ADMIN角色访问。
.anyRequest().authenticated(); // 其他所有的页面和请求登录成功后才可以访问
// 配置登录相关信息
http.formLogin()
.loginPage("/login.html") // 指定登录页面(当我们未登录状态访问一个页面时会自动跳转到此处指定的页面)
.loginProcessingUrl("/api/login") // 指定登录url(默认为/login,此处和表单的action要一致,即登录信息提交到此处指定的url后security才可进行登录处理)
.usernameParameter("username") // 指定登录用户名参数名称(默认为username)
.passwordParameter("password") // 指定密码参数名称(默认为password)
.defaultSuccessUrl("/index.html") // 指定登录成功后跳转页
.failureUrl("/api/login/error") // 指定登录失败URL,可在该接口中抛出异常。(默认为/login?error,就是在controller中写一个接口,登录失败会跳转那个接口)
.permitAll();
// 配置注销相关信息
http.logout()
.logoutUrl("/api/loginout") // 指定注销URL(默认为/login,即需要注销时需要调用此处指定的url后security才可进行注销处理)
.permitAll();
// 关闭CSRF跨域
http.csrf().disable();
}
// 配置对资源文件放行
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**");
}
}
登陆
登陆
欢迎
登陆成功
需要测试:登录功能是否正常使用?html页面是否可以正确被拦截?接口是否可以正确被拦截?
admin/securitytest.html应该是只有角色为ADMIN的用户登录后才能访问。
visitor/securitytest.html无论登不登录都可以随意访问。
@RestController
@RequestMapping("/")
public class SecurityTestController {
/**
* baseTest : Spring Security 基础测试:未登录时访问该接口应该自动转到login.html,登录后访问该接口应返回“springsecurity”
*
* @author [email protected]
* @version 1.0
* @date 2019/5/6 15:02
* @return java.lang.String
* @since JDK 1.8
*/
@GetMapping(value = "securitytest/basetest")
public String baseTest(){
return "spring security";
}
/**
* adminSecurityTest : API测试:未登录时访问该接口应该自动转到login.html,非管理员角色登录后应提示403权限不足,管理员登录后应返回“api测试:admin”
*
* @author [email protected]
* @version 1.0
* @date 2019/5/6 15:11
* @return java.lang.String
* @since JDK 1.8
*/
@GetMapping(value = "admin/securitytest")
public String adminSecurityTest(){
return "api测试:admin";
}
}
可以使用POSTMAN发送GET请求进行测试。
具体的测试可以在github下载该项目进行测试和使用,此处只放两张简单的图!