序:
本文采用springboot+spring security+mybatis来解决登录权限管理的问题。由于是新人,所以在操作方面讲得比较详细。
话不多说,直接来开始我们的项目。
这里我们采用了intellIJidea编辑器。
1.1 创建springboot项目(jdk采用1.8版本)
1.2 对项目进行配置
1.3 勾选我们需要的架包依赖
1.4 对项目进行最终的命名及项目位置确定
2.1 application.properties(我们可以随时修改其中数据库的地址及其他相关数据)
jdbc.db.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.db.url=jdbc:mysql://localhost:3306/mydemo?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
jdbc.db.username=root
jdbc.db.password=admin
jdbc.db.maxActive=500
logging.level.org.springframework.security= INFO
spring.thymeleaf.cache=false
2.2 pom.xml(若你按照我的方式创建项目有可能运行不起来,可能是架包版本的缘故,可复制我的)
4.0.0
com.example
security-mybatis
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.3.0.RELEASE
com.us.Application
1.8
1.8
3.2.7
1.2.2
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-thymeleaf
org.thymeleaf.extras
thymeleaf-extras-springsecurity4
mysql
mysql-connector-java
6.0.5
com.mchange
c3p0
0.9.5.2
commons-logging
commons-logging
org.springframework
spring-jdbc
org.mybatis
mybatis
${mybatis.version}
org.mybatis
mybatis-spring
${mybatis-spring.version}
3.1 数据库表的设计,登录比较简单,只有五张表,分别是用户表(sys_user),角色表(sys_role),权限表(sys_permission),角色权限关系表(sys_role_user),角色用户关系表(sys_role_permission).
然后我们往里面填充一点数据
insert into SYS_USER (id,username, password) values (1,'admin', 'admin');
insert into SYS_USER (id,username, password) values (2,'user', 'user');
insert into SYS_ROLE(id,name) values(1,'ROLE_ADMIN');
insert into SYS_ROLE(id,name) values(2,'ROLE_USER');
insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(1,1);
insert into SYS_ROLE_USER(SYS_USER_ID,ROLES_ID) values(2,2);
BEGIN;
INSERT INTO `Sys_permission` VALUES ('1', 'ROLE_HOME', 'home', '/', null), ('2', 'ROLE_ADMIN', 'user', '/admin', null);
COMMIT;
BEGIN;
INSERT INTO `Sys_permission_role` VALUES ('1', '1', '1'), ('2', '1', '2'), ('3', '2', '1');
COMMIT;
3.2 实体类的创建
3.2.1 SysUser.java
package com.example.securitymybatis.entity;
import java.util.List;
//用户表(sys_user表)
public class SysUser {
//主键id
private Integer id;
//用户名
private String username;
//登录密码
private String password;
private List roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List getRoles() {
return roles;
}
public void setRoles(List roles) {
this.roles = roles;
}
}
3.2.2 SysRole.java
package com.example.securitymybatis.entity;
//角色表(sys_role表)
public class SysRole {
//主键id
private Integer id;
//角色名称
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.2.3 Permission.java
package com.example.securitymybatis.entity;
//权限表(sys_permission表)
public class Permission {
//主键id
private int id;
//权限名称
private String name;
//权限描述
private String descritpion;
//授权链接
private String url;
//父节点id
private int pid;
//请求方式
private String method;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescritpion() {
return descritpion;
}
public void setDescritpion(String descritpion) {
this.descritpion = descritpion;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
3.2.4 Message.java
package com.example.securitymybatis.entity;
//中间信息表(无数据库实体表,只用来专递中间信息)
public class Message {
//信息标题
private String title;
//信息主体
private String content;
//额外信息
private String etraInfo;
public Message(String title, String content, String etraInfo) {
super();
this.title = title;
this.content = content;
this.etraInfo = etraInfo;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getEtraInfo() {
return etraInfo;
}
public void setEtraInfo(String etraInfo) {
this.etraInfo = etraInfo;
}
}
4.1 DBconfig.java配置
package com.example.securitymybatis.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import java.beans.PropertyVetoException;
//配置数据源
@Configuration
public class DBconfig {
@Autowired
private Environment env;
@Bean(name="dataSource")
public ComboPooledDataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(env.getProperty("jdbc.db.driverClassName"));
dataSource.setJdbcUrl(env.getProperty("jdbc.db.url"));
dataSource.setUser(env.getProperty("jdbc.db.username"));
dataSource.setPassword(env.getProperty("jdbc.db.password"));
dataSource.setMaxPoolSize(20);
dataSource.setMinPoolSize(5);
dataSource.setInitialPoolSize(10);
dataSource.setMaxIdleTime(300);
dataSource.setAcquireIncrement(5);
dataSource.setIdleConnectionTestPeriod(60);
return dataSource;
}
}
4.2 MybaitsConfig.java配置
package com.example.securitymybatis.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
//扫描mapper文件
@Configuration
@ComponentScan
public class MybatisConfig {
@Autowired
private DataSource dataSource;
@Bean(name = "sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactory(ApplicationContext applicationContext) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// sessionFactory.setPlugins(new Interceptor[]{new PageInterceptor()});
sessionFactory.setMapperLocations(applicationContext.getResources("classpath*:mapper/*.xml"));
return sessionFactory;
}
}
4.3 MybatisScannerConfig.java配置
package com.example.securitymybatis.config;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//扫描dao层文件
@Configuration
public class MybatisScannerConfig {
@Bean
public MapperScannerConfigurer MapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.example.securitymybatis.dao");
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
return mapperScannerConfigurer;
}
}
4.4 TransactionConfig.java配置
package com.example.securitymybatis.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import javax.sql.DataSource;
//开启事务管理
@Configuration
@ComponentScan
public class TransactionConfig implements TransactionManagementConfigurer {
@Autowired
private DataSource dataSource;
@Bean(name = "transactionManager")
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
4.5 WebMvcConfig.java 配置
package com.example.securitymybatis.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
//web视图管理
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
package com.example.securitymybatis.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
//web视图管理
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
4.6 WebSecurityConfig.java 配置(本文核心配置,请看注释)
package com.example.securitymybatis.config;
import com.example.securitymybatis.security.MyFilterSecurityInterceptor;
import com.example.securitymybatis.security.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
//业务核心
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
@Bean
UserDetailsService customUserService(){ //注册UserDetailsService 的bean
return new UserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()); //user Details Service验证
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// // http.authorizeRequests()每个匹配器按照它们被声明的顺序被考虑。
http
.authorizeRequests()
// 所有用户均可访问的资源
.antMatchers("/css/**", "/js/**", "/images/**", "/webjars/**", "**/favicon.ico").permitAll()
// ROLE_USER的权限才能访问的资源
.antMatchers("/user/**").hasRole("USER")
// 任何尚未匹配的URL只需要验证用户即可访问
.anyRequest().authenticated()
.and()
.formLogin()
// 指定登录页面,授予所有用户访问登录页面
.loginPage("/login")
//设置默认登录成功跳转页面,错误回到login界面
.defaultSuccessUrl("/index").failureUrl("/login?error").permitAll()
.and()
//开启cookie保存用户数据
.rememberMe()
//设置cookie有效期
.tokenValiditySeconds(60 * 60 * 24 * 7)
//设置cookie的私钥
.key("security")
.and()
.logout()
.permitAll();
//登录拦截器
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)
//springsecurity4自动开启csrf(跨站请求伪造)与restful冲突
.csrf().disable();
}
}
完成config后我们开始从自底向上的方式来编写我们的登陆功能。
5.1 UserDaoMapper.xml 配置
5.2 PermissionDaoMapper.xml 配置
6.1 UserDao.java
package com.example.securitymybatis.dao;
import com.example.securitymybatis.entity.SysUser;
public interface UserDao {
public SysUser findByUserName(String username);
}
6.2 PermissionDao.java
package com.example.securitymybatis.dao;
import com.example.securitymybatis.entity.Permission;
import java.util.List;
public interface PermissionDao {
public List findAll();
public List findByAdminUserId(int userId);
}
7.1 UserService.java(其中会用到后面写到的方法)
package com.example.securitymybatis.security;
import com.example.securitymybatis.dao.PermissionDao;
import com.example.securitymybatis.dao.UserDao;
import com.example.securitymybatis.entity.Permission;
import com.example.securitymybatis.entity.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 org.springframework.security.core.userdetails.User;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService implements UserDetailsService { //自定义UserDetailsService 接口
@Autowired
UserDao userDao;
@Autowired
PermissionDao permissionDao;
@Override
public UserDetails loadUserByUsername(String username) { //重写loadUserByUsername 方法获得 userdetails 类型用户
SysUser user = userDao.findByUserName(username);
if (user != null) {
List permissions = permissionDao.findByAdminUserId(user.getId());
List grantedAuthorities = new ArrayList <>();
for (Permission permission : permissions) {
if (permission != null && permission.getName()!=null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
//1:此处将权限信息添加到 GrantedAuthority 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。
grantedAuthorities.add(grantedAuthority);
}
}
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("admin: " + username + " do not exist!");
}
}
}
8.1 LoginController.java(其中有几个方法的权限是管理员才有的)
package com.example.securitymybatis.controller;
import com.example.securitymybatis.entity.Message;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LoginController {
@RequestMapping("/index")
public String index(Model model){
Message msg = new Message("测试标题","测试内容","额外信息,只对管理员显示");
model.addAttribute("msg", msg);
return "index";
}
@RequestMapping("/admin")
@ResponseBody
public String hello(){
return "hello admin";
}
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping(value = "/user", method = RequestMethod.GET)
@ResponseBody
public String getList(){
return "hello getList";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody
public String save(){
return "hello save";
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
@ResponseBody
public String update(){
return "hello update";
}
}
9.1 MyAccessDecisionManager.java(授权管理器)
package com.example.securitymybatis.security;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Iterator;
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
// decide 方法是判定是否拥有权限的决策方法,
//authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
//object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
//configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(null== configAttributes || configAttributes.size() <=0) {
return;
}
ConfigAttribute c;
String needRole;
for(Iterator iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
if(needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("no right");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class> clazz) {
return true;
}
}
9.2
MyFilterSecurityInterceptor.java (自定义拦截器)
package com.example.securitymybatis.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import javax.servlet.*;
import java.io.IOException;
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}
9.3 MyInvocationSecurityMetadataSourceService.java(获取被拦截url所需的全部权限的方法)
package com.example.securitymybatis.security;
import com.example.securitymybatis.dao.PermissionDao;
import com.example.securitymybatis.entity.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@Service
public class MyInvocationSecurityMetadataSourceService implements
FilterInvocationSecurityMetadataSource {
@Autowired
private PermissionDao permissionDao;
private HashMap> map =null;
/**
* 加载权限表中所有权限
*/
public void loadResourceDefine(){
map = new HashMap<>();
Collection array;
ConfigAttribute cfg;
List permissions = permissionDao.findAll();
for(Permission permission : permissions) {
array = new ArrayList<>();
cfg = new SecurityConfig(permission.getName());
//此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
array.add(cfg);
//用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
map.put(permission.getUrl(), array);
}
}
//此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
@Override
public Collection getAttributes(Object object) throws IllegalArgumentException {
if(map ==null) loadResourceDefine();
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
AntPathRequestMatcher matcher;
String resUrl;
for(Iterator iter = map.keySet().iterator(); iter.hasNext(); ) {
resUrl = iter.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
return map.get(resUrl);
}
}
return null;
}
@Override
public Collection getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class> clazz) {
return true;
}
}
如上所示security的拦截器就配好了,如果有看不懂的地方,可在网上搜security拦截器,我这里就不一一详细解释了。
10.1 login.html
登录页面
已成功注销
有错误,请重试
使用账号密码登录
10.2 index.html
恭喜您,您有 ROLE_ADMIN 权限
整个项目的文件结构如上图所示,static下的css文件夹中只有一个bootstrap.min.css文件,网上都能搜到,我就不上传了。
11.1 运行项目,点击右上角的绿色三角形运行项目,不需要配置tomcat.
11.2 打开浏览器,输入localhost:8080即可进入登录界面
11.3 管理员和普通用户登录显示不同的内容
11.3.1 管理员登录显示
11.3.2 普通用户登录显示
11.4 点击管理员登录后跳转界面上的admin,页面会显示“hello admin”,而普通用户会有403错误。
11.4.1 管理员点击admin
11.4.2 普通用户点击admin
至此,整个springboot+springsecurity+mybaits项目就搭建好了。