在第二章的时候我们已经在我们的配置文件中设置了文件权限放行的功能,在本章我们将讲解如何基于mongodb数据库实现security,基于上一章我们编写的代码本章我们将实现security的权限控制。
1、首先在我们的sys的entity目录底下我们新建一个UserRole、User以及QueryUser实体类,类信息如下:
package com.mongo.sys.entity;
import com.mongo.common.base.entity.QueryField;
import org.bson.types.ObjectId;
/*
* 类描述:用户角色实体类
* @auther linzf
* @create 2018/3/30 0030
*/
public class UserRole {
private ObjectId id;
// 增加QueryField注解在buildBaseQuery构建Query查询条件的时候会自动将其加入到Query查询条件中
@QueryField
private String name;
private String roleName;
public String getId() {
return id.toString();
}
public void setId(String id) {
this.id = new ObjectId(id);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
package com.mongo.sys.entity;
import com.mongo.common.base.entity.QueryField;
import org.bson.types.ObjectId;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/*
* 类描述:用户实体类
* @auther linzf
* @create 2018/3/30 0030
*/
public class User implements UserDetails {
private ObjectId id;
// 增加QueryField注解在buildBaseQuery构建Query查询条件的时候会自动将其加入到Query查询条件中
@QueryField
private String login;
private String password;
private String userName;
private String address;
private String job;
private Date birthDate;
private String city;
private String district;
private String province;
private String streetAddress;
private String state;
private String type;
private Date lastLoginDate;
// 用户角色信息
private List<UserRole> roles;
// 角色信息集合
private String roleArray;
public String getRoleArray() {
return roleArray;
}
public void setRoleArray(String roleArray) {
this.roleArray = roleArray;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
if(this.getRoles()!=null){
List<UserRole> roles=this.getRoles();
for(UserRole role:roles){
if(role.getName()!=null){
auths.add(new SimpleGrantedAuthority(role.getName()));
}
}
}
return auths;
}
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return this.getLogin();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public String getId() {
return id.toString();
}
public void setId(String id) {
this.id = new ObjectId(id);
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public void setPassword(String password) {
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getDistrict() {
return district;
}
public void setDistrict(String district) {
this.district = district;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getStreetAddress() {
return streetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Date getLastLoginDate() {
return lastLoginDate;
}
public void setLastLoginDate(Date lastLoginDate) {
this.lastLoginDate = lastLoginDate;
}
public List<UserRole> getRoles() {
return roles;
}
public void setRoles(List<UserRole> roles) {
this.roles = roles;
}
}
package com.mongo.sys.entity;
import com.mongo.common.base.entity.QueryBase;
import com.mongo.common.base.entity.QueryField;
import com.mongo.common.base.entity.QueryType;
import org.bson.types.ObjectId;
public class QueryUser extends QueryBase {
@QueryField(type = QueryType.LIKE)
private String userName;
@QueryField(type = QueryType.LIKE)
private String login;
@QueryField(type = QueryType.LIKE)
private String job;
private ObjectId groupId;
public String getGroupId() {
if(groupId==null){
return "";
}else{
return groupId.toString();
}
}
public void setGroupId(String groupId) {
if(groupId!=null&&!groupId.equals("")){
this.groupId = new ObjectId(groupId);
}else{
this.groupId = null;
}
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
大家在构建实体类的时候,若我们的定义的字段类型是ObjectId类型那么大家记得set和get方法要做成如下的改造,否则将导致我们的程序在获取数据的时候报错:
private ObjectId groupId;
public String getGroupId() {
if(groupId==null){
return "";
}else{
return groupId.toString();
}
}
public void setGroupId(String groupId) {
if(groupId!=null&&!groupId.equals("")){
this.groupId = new ObjectId(groupId);
}else{
this.groupId = null;
}
}
2、接着在我们的dao目录底下增加我们的UserDao数据库操作类,我们的UserDao数据库操作类继承了我们上一章所编写的MongodbBaseDao抽象实现类,大家通过看下面的代码会发现,实际上我们并没有写多少代码,但是我们已经实现了用户的增删改查等这些通用的功能。
package com.mongo.sys.dao;
import com.mongo.common.base.dao.MongodbBaseDao;
import com.mongo.common.base.entity.Pagination;
import com.mongo.sys.entity.QueryUser;
import com.mongo.sys.entity.User;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.util.List;
/*
* 类描述:实现用户管理的dao
* @auther linzf
* @create 2018/3/30 0030
*/
@Component
public class UserDao extends MongodbBaseDao<User,QueryUser> {
/**
* 功能描述:根据账号来获取用户信息
* @param login
* @return
*/
public User findByLogin(String login){
Query query=new Query(Criteria.where("login").is(login));
User user = mongoTemplate.findOne(query , User.class);
return user;
}
/**
* 功能描述:更新用户状态为可用或者不可用
* @param user
* @return
*/
public void userControl(User user){
Query query=new Query(Criteria.where("id").is(user.getId()));
Update update= new Update().set("state", user.getState());
mongoTemplate.updateFirst(query,update,User.class);
}
@Override
public Pagination<User> findByPage(QueryUser queryUser) {
Query query = buildBaseQuery(queryUser);
if(queryUser.getGroupId()!=null&&!queryUser.getGroupId().equals("")){
query.addCriteria(Criteria.where("orgGroup.id").is(new ObjectId(queryUser.getGroupId())));
}
//获取总条数
long totalCount = this.mongoTemplate.count(query, this.getEntityClass());
//总页数
int totalPage = (int) (totalCount/queryUser.getLimit());
int skip = (queryUser.getPage()-1)*queryUser.getLimit();
Pagination<User> page = new Pagination(queryUser.getPage(), totalPage, (int)totalCount);
query.skip(skip);// skip相当于从那条记录开始
query.limit(queryUser.getLimit());// 从skip开始,取多少条记录
List<User> data = this.find(query);
page.build(data);//获取数据
return page;
}
}
3、开始集成我们的security,我们的spring boot的版本已经升级到了2.0版本,我们的security的版本也同步升级到了5.x版本,在5.x版本security已经废弃了好多5.x之前的东西,因此我们的security的配置也与之前的配置有了很多不同之处,比如我们的密码加密、设置放行目录和地址等,在这里就不再细说了,大家有兴趣可以直接去sprng security的官网直接了解新版本的不同之处,3.x标识的文件都是存放在config的security目录底下。
3.1、LoginSuccessHandle:实现登陆成功以后根据不同的权限控制页面的跳转,代码如下:
package com.mongo.common.config.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
/*
* 类描述:实现根据不同的权限实现登陆的时候页面的跳转
* @auther linzf
* @create 2018/04/13 0013
*/
public class LoginSuccessHandle implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
String path = request.getContextPath() ;
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
if (roles.contains("ROLE_DINER")){
response.sendRedirect(basePath+"diningTable");
return;
}
response.sendRedirect(basePath+"main");
}
}
3.2、CustomUserService:主要实现自有的权限架构的用户的登陆业务逻辑的实现,代码如下:
package com.mongo.common.config.security;
import com.mongo.sys.dao.UserDao;
import com.mongo.sys.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Date;
/**
* Created by Administrator on 2018/04/17 0004.
*/
public class CustomUserService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userDao.findByLogin(s);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
// 用户登陆以后更新用户的最迟登陆时间
Query query = new Query(Criteria.where("id").is(user.getId()));
Update update= new Update().set("lastLoginDate", new Date());
userDao.update(query,update);
// 自定义错误的文章说明的地址:http://blog.csdn.net/z69183787/article/details/21190639?locationNum=1&fps=1
if(user.getState()==null||user.getState().equalsIgnoreCase("0")){
throw new LockedException("用户账号被冻结,无法登陆请联系管理员!");
}
return user;
}
}
3.3、CustomPasswordEncoder:实现用户的密码加密,以及密码验证的功能,在security5.x版本已经弃用了之前的MD5密码加密功能,在该版本默认使用的是bcrypt加密功能进行密码的加密,若大家还是喜欢以前的密码加密功能,那就自己去实现密码的盐值加密,以及盐值的验证就可以了,此处默认使用的是bcrypt的加密方式进行密码的加密,代码如下:
package com.mongo.common.config.security;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* spring-security登陆的密码进行bcrypt加密传到数据库
*/
public class CustomPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return passwordEncoder.encode(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return passwordEncoder.matches(rawPassword.toString(),encodedPassword);
}
}
3.4、WebSecurityConfig:实现security的权限控制,以及页面放行,csrf(跨域访问)放行、iframe放行等权限的配置,代码如下:
package com.mongo.common.config.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 实现Security的配置
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 功能描述:实现自有的用户的登陆的验证
* @return
*/
@Bean
UserDetailsService customUserService(){
return new CustomUserService();
}
/**
* 功能描述:实现密码的加密和验证
* @return
*/
@Bean
PasswordEncoder passwordEncoder(){
return new CustomPasswordEncoder();
}
/**
* 功能描述:实现登陆成功以后页面的跳转
* @return
*/
@Bean
LoginSuccessHandle loginSuccessHandle(){return new LoginSuccessHandle();}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* 描述:csrf().disable()为了关闭跨域访问的限制,若不关闭则websocket无法与后台进行连接
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
// 设置放行目录
http.authorizeRequests().antMatchers("/api/*","/css/*","/js/*","/images/*","/fonts/*","/font-awesome/*").permitAll();
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/main")
.successHandler(loginSuccessHandle())
.failureUrl("/login?error=true")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login").
permitAll();
}
}
到此为止我们已经完成了spring security的集成了,接着我们集成springmvc的页面跳转部分,在我们的config的webmvc目录底下创建WebMvcConfig配置文件,代码如下:
package com.mongo.common.config.webmvc;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* 类描述:springMVC的配置
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 功能描述:配置放行的静态文件的目录
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
registry.addResourceHandler("/fonts/**").addResourceLocations("classpath:/static/fonts/");
registry.addResourceHandler("/font-awesome/**").addResourceLocations("classpath:/static/font-awesome/");
}
/**
* 重写方法描述:实现在url中输入相应的地址的时候直接跳转到某个地址
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/main").setViewName("main");
registry.addViewController("/error").setViewName("error");
registry.addViewController("/home").setViewName("home");
registry.addViewController("/upload").setViewName("/sys/upload/upload");
registry.addViewController("/dictList").setViewName("/sys/dict/dictList");
registry.addViewController("/userRoleList").setViewName("/sys/role/roleList");
registry.addViewController("/groupList").setViewName("/sys/orggroup/groupList");
registry.addViewController("/userList").setViewName("/sys/user/userList");
registry.addViewController("/treeList").setViewName("/sys/tree/treeList");
}
}
这样我们就完成了我们的spring security的全部集成,我们可以试着将我们的项目启动起来,大家直接访问以下的地址,若可以看到以下的页面说明我们的spring security已经集成完成了,静态的文件资源大家直接去我的github下面提供的地址去下载就好了,以及导入到mongodb数据库的文件也在我的当前工程的GitHub的db文件夹里,大家自己去拿,这里就不再贴出来了【导入mongodb数据库的脚本:./mongoexport -u order -p hyll-2.0 -d test -c user -o /home/user.dat】。
接着大家输入账号:fjhyll和密码:123456,大家可以看到直接登陆成功画面说明我们已经完成了security的集成了。
到此为止的GitHub项目地址:https://github.com/185594-5-27/csdn/tree/master-base-4
上一篇文章地址:基于spring boot和mongodb打造一套完整的权限架构(三)【抽象实现类和swagger的实现】
下一篇文章地址:基于spring boot和mongodb打造一套完整的权限架构(五)【集成用户模块、菜单模块、角色模块】
QQ交流群:578746866