本博客基于上一个增加了角色的权限表, 可以进行权限校验
一, 数据准备
1, 数据表建立
/*
Navicat MySQL Data Transfer
Source Server : 本地
Source Host : localhost:3306
Source Database : test
Target Server Type : MYSQL
Date: 2017-8-14 22:17:33
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `sys_user`
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`username` varchar(32) DEFAULT NULL COMMENT '用户名',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `sys_role`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(32) DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `sys_permission`
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` INT (32) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(32) DEFAULT NULL COMMENT '用户名',
`desc` VARCHAR (32) DEFAULT NULL COMMENT '描述',
`url` VARCHAR (32) DEFAULT NULL COMMENT 'url',
`pid` INT (32),
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `sys_role_user`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_user`;
CREATE TABLE `sys_role_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`sys_user_id` INT(32) NOT NULL COMMENT 'user_id',
`sys_role_id` INT(32) NOT NULL COMMENT 'role_id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
ALTER TABLE sys_role_user ADD CONSTRAINT sys_FK1 FOREIGN KEY(sys_user_id) REFERENCES sys_user(id);
ALTER TABLE sys_role_user ADD CONSTRAINT role_FK2 FOREIGN KEY(sys_role_id) REFERENCES sys_role(id);
-- ----------------------------
-- Table structure for `sys_permission_role`
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission_role`;
CREATE TABLE `sys_permission_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`sys_role_id` INT(32) NOT NULL COMMENT 'role_id',
`sys_permission_id` INT(32) NOT NULL COMMENT 'permission_id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
ALTER TABLE sys_permission_role ADD CONSTRAINT sys_FK3 FOREIGN KEY(sys_role_id) REFERENCES sys_role(id);
ALTER TABLE sys_permission_role ADD CONSTRAINT role_FK4 FOREIGN KEY(sys_permission_id) REFERENCES sys_permission(id);
2, 导入数据
insert into SYS_USER (id,username, password) values (1,'vini', '123');
insert into SYS_USER (id,username, password) values (2,'bronk', '123');
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,sys_role_id) values(1,1);
insert into SYS_ROLE_USER(SYS_USER_ID,sys_role_id) values(2,2);
BEGIN;
INSERT INTO `sys_permission` VALUES ('1', 'ROLE_HOME', 'home', '/', null),
('2', 'ROLE_ADMIN', 'ABel', '/admin', null);
COMMIT;
BEGIN;
INSERT INTO `sys_permission_role` VALUES ('1', '1', '1'), ('2', '1', '2'), ('3', '2', '1');
COMMIT;
3, mybatis实体, 其余2个和上一篇博客一样
SysPermission.groovy
package com.wenbronk.security.entity
/**
* Created by wenbronk on 2017/8/17.
*/
class SysPermission {
int id
String name
String desc
String url
int pid
}
4, application.yml配置服务启动导入
和上个一样
二, security部分
东西比较多, 按流程来,
1, WebSecurityConfig.groovy 添加
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)
最终代码为:
package com.wenbronk.security.security.config
import com.wenbronk.security.security.interceptor.MyFilterSecurityInterceptor
import com.wenbronk.security.security.service.CustomUserService
import org.springframework.beans.factory.annotation.Autowired
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.web.access.intercept.FilterSecurityInterceptor
import javax.inject.Inject
/**
* Created by wenbronk on 2017/8/15.
*/
@Configuration
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Inject
CustomUserService customUserService;
@Autowired
MyFilterSecurityInterceptor myFilterSecurityInterceptor
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() // 任何请求都拦截
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.permitAll() // 登陆后可访问任意页面
.and()
.logout().permitAll(); // 注销后任意访问
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class)
}
}
2, 请求会被拦截器拦截, MyFilterSecurityInterceptor,
package com.wenbronk.security.security.interceptor
import com.wenbronk.security.security.config.MyAccessDecisonManager
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.Component
import javax.inject.Inject
import javax.servlet.*
/**
* Created by wenbronk on 2017/8/17.
*/
@Component
class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Inject
FilterInvocationSecurityMetadataSource securityMetadataSource
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisonManager myAccessDecisonManager) {
super.setAccessDecisionManager(myAccessDecisonManager)
}
@Override
void init(FilterConfig filterConfig) throws ServletException {
}
/**
* fi中有一个被拦截的url
* 里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
*/
@Override
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain)
invoke(fi)
}
void invoke(FilterInvocation filterInvocation) {
InterceptorStatusToken token = super.beforeInvocation(filterInvocation)
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse())
} finally {
super.afterInvocation(token, null)
}
}
@Override
void destroy() {
}
@Override
Class<?> getSecureObjectClass() {
return FilterInvocation.class
}
@Override
SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource
}
}
3, 拦截器会调用 MyInvocationSecurityMetaDataSource的 getAttribute方法 获取 filter的权限, 并且滴哦啊用 MyAccessDecisionManager的 decide 方法来校验是否拥有权限
MyInvocationSecurityMetadataSource.groovy
package com.wenbronk.security.security.service
import com.wenbronk.security.entity.SysPermission
import com.wenbronk.security.mapper.SysPermissionMapper
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.inject.Inject
import javax.servlet.http.HttpServletRequest
/**
* Created by wenbronk on 2017/8/17.
*/
@Service
class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource{
@Inject
SysPermissionMapper sysPermissionMapper
/**
* 此方法是为了判定用户请求的url 是否在权限表中,
* 如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
*/
@Override
Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
Map<String, Collection<ConfigAttribute>> map = loadResourceDefine()
HttpServletRequest request = ((FilterInvocation) o).getHttpRequest()
map.each {entry ->
AntPathRequestMatcher matcher = new AntPathRequestMatcher(entry.getKey())
if (matcher.matches(request)) {
// 匹配, 返回给decide方法
return entry.getValue()
}
}
return null
}
/**
* 加载权限表中所有的权限
*/
public Map<String, Collection<ConfigAttribute>> loadResourceDefine() {
Map<String, Collection<ConfigAttribute>> map = new HashMap<>()
List<SysPermission> permissions = sysPermissionMapper.findAll()
permissions.each {permission ->
List<ConfigAttribute> array = new ArrayList<>()
// 此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。
// 此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
ConfigAttribute cfg = new SecurityConfig(permission.getName())
array.add(cfg)
// 用权限的url作为key, 权限的名称集合为value
map.put(permission.getUrl(), array);
}
return null
}
@Override
Collection<ConfigAttribute> getAllConfigAttributes() {
return null
}
@Override
boolean supports(Class<?> aClass) {
return true
}
}
4, MyInvocationSecurityMetadataSource 查询数据库中url和所需权限的关系, 返回给 MyAccessDecisionManger
MyAccessDecisionManger.groovy
package com.wenbronk.security.security.config
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.stereotype.Service
/**
* Created by wenbronk on 2017/8/17.
*/
@Service
class MyAccessDecisonManager implements AccessDecisionManager {
/**
* 判断是否拥有权限的决策方法
* authentication 是CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
* object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,
*/
@Override
void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if (null == configAttributes || configAttributes.size() <=0 )
return
configAttributes.each {configAttribute ->
String needRole = configAttribute.getAttribute()
authentication.getAuthorities().each {authority ->
if (needRole.trim().equals(authority.getAuthority()))
return
}
}
throw new java.nio.file.AccessDeniedException('no right')
}
@Override
boolean supports(ConfigAttribute configAttribute) {
return true
}
@Override
boolean supports(Class<?> aClass) {
return true
}
}
decide进行是否有权限的决策, 正常则返回, 没有就抛出异常
5, 最终在 customuserService进行权限和用户的装配, 准备返回给前端
package com.wenbronk.security.security.service
import com.wenbronk.security.entity.SysPermission
import com.wenbronk.security.entity.SysUser
import com.wenbronk.security.mapper.SysPermissionMapper
import com.wenbronk.security.mapper.SysUserMapper
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 javax.inject.Inject
/**
* Created by wenbronk on 2017/8/15.
*/
@Service
class CustomUserService implements UserDetailsService {
@Inject
SysUserMapper sysUserMapper
@Inject
SysPermissionMapper sysPermissionMapper
@Override
UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
def sysUser = sysUserMapper.findByUserName(s) as SysUser
if (sysUser != null) {
List<SysPermission> permissions = sysPermissionMapper.findByAdminUserId(sysUser.getId())
List<GrantedAuthority> grantedAuthorities = new ArrayList<>()
permissions.each {permission ->
if (permission != null && permission.getName() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName())
// 此处将权限信息添加到 GrantedAuthority 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。
grantedAuthorities.add(grantedAuthority)
}
}
return new User(sysUser.getUsername(), sysUser.getPassword(), grantedAuthorities)
}else {
throw new UsernameNotFoundException('admin: ' + s + ' do not exits')
}
}
}
三: 页面部分
1, 页面转向config, 和上一篇的一样
@Configuration
class WebMvcConfig extends WebMvcConfigurerAdapter{
@Override
void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login")
// registry.addViewController("/home").setViewName("home")
}
}
2, 更改home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title sec:authentication="name"></title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<style type="text/css">
body {
padding-top: 50px;
}
.starter-template {
padding: 40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security演示</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a th:href="@{/}"> 首页 </a></li>
<li><a th:href="@{/admin}"> admin </a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container">
<div class="starter-template">
<h1 th:text="${msg.title}"></h1>
<p class="bg-primary" th:text="${msg.content}"></p>
<div sec:authorize="hasRole('ROLE_HOME')"> <!-- 用户类型为ROLE_ADMIN 显示 -->
<p class="bg-info" th:text="${msg.etraInfo}"></p>
</div>
<div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 用户类型为 ROLE_USER 显示 -->
<p class="bg-info">恭喜, 有ROLE_ADMIN的权限</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" class="btn btn-primary" value="注销"/>
</form>
</div>
</div>
</body>
</html>
四, 密码进行加密
注意, 如果使用, 存储进数据库的密码也必须是同一个算法计算的密文
md5, 工具类
package com.wenbronk.security.tools
import java.security.MessageDigest
/**
* Created by wenbronk on 2017/8/17.
*/
class MD5Utils {
private static final String SALT = "tamboo";
public static String encode(String password) {
password = password + SALT;
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
throw new RuntimeException(e);
}
char[] charArray = password.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
public static void main(String[] args) {
System.out.println(MD5Utils.encode("abel"));
}
}
2, 修改 webSecurityConfig的加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService()).passwordEncoder(new PasswordEncoder(){
@Override
public String encode(CharSequence rawPassword) {
return MD5Util.encode((String)rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5Util.encode((String)rawPassword));
}}); //user Details Service验证
}
BCrypt的强hash算法
BCrypt强哈希方法 每次加密的结果都不一样。但是存贮其中一次加密结果 也能够验证成功
1, 修改WebSecurityConfig
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserService).passwordEncoder(new BCryptPasswordEncoder());
}
2, 进行加密
public SysUser create(User u user){
//进行加密
BCryptPasswordEncoder encoder =new BCryptPasswordEncoder();
sysUser.setPassword(encoder.encode(user.getRawPassword().trim()));
userDao.create(user);
return sysUser;