diff --git a/pom.xml b/pom.xml index dc51a9b..817de48 100644 --- a/pom.xml +++ b/pom.xml @@ -28,10 +28,11 @@ org.springframework.boot spring-boot-starter-freemarker - + org.springframework.boot spring-boot-starter-security - --> + org.springframework.boot spring-boot-starter-web @@ -46,16 +47,6 @@ h2 runtime - - org.json @@ -109,9 +100,6 @@ org.springframework.boot spring-boot-maven-plugin - - false - diff --git a/src/main/java/com/yaoyuan/jiscuss/config/WebSecurityConfig.java b/src/main/java/com/yaoyuan/jiscuss/config/WebSecurityConfig.java new file mode 100644 index 0000000..5f95448 --- /dev/null +++ b/src/main/java/com/yaoyuan/jiscuss/config/WebSecurityConfig.java @@ -0,0 +1,98 @@ +package com.yaoyuan.jiscuss.config; + +import com.yaoyuan.jiscuss.handler.CustomAccessDeniedHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +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.builders.WebSecurity; +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.crypto.bcrypt.BCryptPasswordEncoder; + +/** +prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..] +secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用 +jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用. + */ +@Configurable +@EnableWebSecurity +//开启 Spring Security 方法级安全注解 @EnableGlobalMethodSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ + + @Autowired + private CustomAccessDeniedHandler customAccessDeniedHandler; + @Qualifier("userDetailServiceImpl") + @Autowired + private UserDetailsService userDetailsService; + + /** + * 静态资源设置 + */ + @Override + public void configure(WebSecurity webSecurity) { + //不拦截静态资源,所有用户均可访问的资源 + webSecurity.ignoring().antMatchers( + "/", + "/css/**", + "/js/**", + "/images/**", + "/static/**", + "/index/**" + ); + } + /** + * http请求设置 + */ + @Override + public void configure(HttpSecurity http) throws Exception { + //http.csrf().disable(); //注释就是使用 csrf 功能 + http.headers().frameOptions().disable();//解决 in a frame because it set 'X-Frame-Options' to 'DENY' 问题 + //http.anonymous().disable(); + http.authorizeRequests() + .antMatchers("/login/**","/initUserData")//不拦截登录相关方法 + .permitAll() + //.antMatchers("/user").hasRole("ADMIN") // user接口只有ADMIN角色的可以访问 +// .anyRequest() +// .authenticated()// 任何尚未匹配的URL只需要验证用户即可访问 + .anyRequest() + .access("@rbacPermission.hasPermission(request, authentication)")//根据账号权限访问 + .and() + .formLogin() +// .loginPage("/") + .loginPage("/login") //登录请求页 + .loginProcessingUrl("/login") //登录POST请求路径 + .usernameParameter("username") //登录用户名参数 + .passwordParameter("password") //登录密码参数 + .defaultSuccessUrl("/") //默认登录成功页面 + .and() + .exceptionHandling() + .accessDeniedHandler(customAccessDeniedHandler) //无权限处理器 + .and() + .logout() + .logoutSuccessUrl("/login?logout"); //退出登录成功URL + + } + /** + * 自定义获取用户信息接口 + */ + @Override + public void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } + + /** + * 密码加密算法 + * @return + */ + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + + } +} diff --git a/src/main/java/com/yaoyuan/jiscuss/controller/UserSystemController.java b/src/main/java/com/yaoyuan/jiscuss/controller/UserSystemController.java index d233c38..6fb0dd2 100644 --- a/src/main/java/com/yaoyuan/jiscuss/controller/UserSystemController.java +++ b/src/main/java/com/yaoyuan/jiscuss/controller/UserSystemController.java @@ -11,17 +11,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.ArrayList; +import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -51,7 +54,29 @@ public class UserSystemController { List useral2 = usersService.getAllList(); logger.info(">>> 第二遍的全部用户:"+useral2); - + + String username =""; + //获得session对象 + HttpSession session = request.getSession(); + //取出session域中所有属性名 + Enumeration attributeNames = session.getAttributeNames(); + while (attributeNames.hasMoreElements()) { + System.out.println(attributeNames.nextElement()); + } + //SPRING_SECURITY_CONTEXT + Object spring_security_context = session.getAttribute("SPRING_SECURITY_CONTEXT"); + System.out.println(spring_security_context); + SecurityContext securityContext = (SecurityContext) spring_security_context; + if(securityContext!=null){ + //获得认证信息 + Authentication authentication = securityContext.getAuthentication(); + //获得用户详情 + Object principal = authentication.getPrincipal(); + User user = (User) principal; + username = user.getUsername(); + System.out.println(username); + } + //分页获取主题帖子 // List allDiscussions = discussionsService.getAllList(); @@ -63,19 +88,17 @@ public class UserSystemController { //获取主题帖子的分页数据 List pageNumList = getPageNumList(allDiscussionsPage.getTotalPages()); - - + //获取所有标签(以后尝试去缓存中取) List allTags = tagsService.getAllList(); logger.info("全部标签==>:{}",allTags); - HttpSession session=request.getSession(); System.out.println(userall.toString()); map.put("data", "Jiscuss 用户"); map.put("allDiscussions", allDiscussions); map.put("pageDiscussions", pageNumList); map.put("allTags", allTags); - map.put("username", session.getAttribute("username")); + map.put("username", username); return "index"; } @@ -99,25 +122,41 @@ public class UserSystemController { //登录 - @PostMapping(value = "/login") - @ResponseBody - public String login(@RequestParam("username") String username, @RequestParam("password") String password, - HttpSession session) { - JSONObject resultobj = new JSONObject(); - Users user = usersService.checkByUsernameAndPassword(username, password); - if (user!=null) { - //用户名和密码完成校验 - session.setAttribute("username", username); //缓存session - session.setAttribute("userid", user.getId()); //缓存session - resultobj.put("username", username); - resultobj.put("msg", "登录成功"); - resultobj.put("flag", true); - } else { - //用户名和密码未完成校验 - resultobj.put("msg", "用户名或密码错误"); - resultobj.put("flag", false); +// @PostMapping(value = "/login") +// @ResponseBody +// public String login(@RequestParam("username") String username, @RequestParam("password") String password, +// HttpSession session) { +// JSONObject resultobj = new JSONObject(); +// Users user = usersService.checkByUsernameAndPassword(username, password); +// if (user!=null) { +// //用户名和密码完成校验 +// session.setAttribute("username", username); //缓存session +// session.setAttribute("userid", user.getId()); //缓存session +// resultobj.put("username", username); +// resultobj.put("msg", "登录成功"); +// resultobj.put("flag", true); +// } else { +// //用户名和密码未完成校验 +// resultobj.put("msg", "用户名或密码错误"); +// resultobj.put("flag", false); +// } +// return resultobj.toString(); +// } + + //登录页 + @GetMapping("/login") + public String login(@RequestParam(value = "error", required = false) String error, + @RequestParam(value = "logout", required = false) String logout,Map map) { + if (error != null) { + map.put("msg", "您输入的用户名密码错误!"); + return "login"; } - return resultobj.toString(); + if (logout != null) { + map.put("msg", "退出成功!"); + return "login"; + } + + return "login"; } //退出 diff --git a/src/main/java/com/yaoyuan/jiscuss/entity/Users.java b/src/main/java/com/yaoyuan/jiscuss/entity/Users.java index 27aac52..32fc408 100644 --- a/src/main/java/com/yaoyuan/jiscuss/entity/Users.java +++ b/src/main/java/com/yaoyuan/jiscuss/entity/Users.java @@ -3,7 +3,9 @@ package com.yaoyuan.jiscuss.entity; import java.io.Serializable; +import java.util.Collection; import java.util.Date; +import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; @@ -16,11 +18,13 @@ import javax.persistence.Table; import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; @Data @Entity @Table(name="users") -public class Users implements Serializable { +public class Users implements Serializable { private static final long serialVersionUID = 1L; @Id @@ -65,7 +69,5 @@ public class Users implements Serializable { @Column(name="level") private Integer level; - - - + } \ No newline at end of file diff --git a/src/main/java/com/yaoyuan/jiscuss/handler/CustomAccessDeniedHandler.java b/src/main/java/com/yaoyuan/jiscuss/handler/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..677e001 --- /dev/null +++ b/src/main/java/com/yaoyuan/jiscuss/handler/CustomAccessDeniedHandler.java @@ -0,0 +1,64 @@ +package com.yaoyuan.jiscuss.handler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.WebAttributes; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * 处理无权请求 + * @author charlie + * + */ +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + private Logger log = LoggerFactory.getLogger(CustomAccessDeniedHandler.class); + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException, ServletException { + boolean isAjax = ControllerTools.isAjaxRequest(request); + System.out.println("CustomAccessDeniedHandler handle"); + if (!response.isCommitted()) { + if (isAjax) { + String msg = accessDeniedException.getMessage(); + log.info("accessDeniedException.message:" + msg); + String accessDenyMsg = "{\"code\":\"403\",\"msg\":\"没有权限\"}"; + ControllerTools.print(response, accessDenyMsg); + } else { + request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException); + response.setStatus(HttpStatus.FORBIDDEN.value()); + RequestDispatcher dispatcher = request.getRequestDispatcher("/403"); + dispatcher.forward(request, response); + } + } + + } + + public static class ControllerTools { + public static boolean isAjaxRequest(HttpServletRequest request) { + return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); + } + + public static void print(HttpServletResponse response, String msg) throws IOException { + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json; charset=utf-8"); + PrintWriter writer = response.getWriter(); + writer.write(msg); + writer.flush(); + writer.close(); + } + } + +} diff --git a/src/main/java/com/yaoyuan/jiscuss/handler/RbacPermission.java b/src/main/java/com/yaoyuan/jiscuss/handler/RbacPermission.java new file mode 100644 index 0000000..c08168b --- /dev/null +++ b/src/main/java/com/yaoyuan/jiscuss/handler/RbacPermission.java @@ -0,0 +1,38 @@ +package com.yaoyuan.jiscuss.handler; + +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * RBAC数据模型控制权限 + * @author charlie + * + */ +@Component("rbacPermission") +public class RbacPermission{ + + private AntPathMatcher antPathMatcher = new AntPathMatcher(); + + public boolean hasPermission(HttpServletRequest request, Authentication authentication) { + Object principal = authentication.getPrincipal(); + boolean hasPermission = false; + + hasPermission = true; +// if (principal instanceof UserEntity) { +// // 读取用户所拥有的权限菜单 +// List menus = ((UserEntity) principal).getRoleMenus(); +// System.out.println(menus.size()); +// for (Menu menu : menus) { +// if (antPathMatcher.match(menu.getMenuUrl(), request.getRequestURI())) { +// hasPermission = true; +// break; +// } +// } +// } + return hasPermission; + } +} diff --git a/src/main/java/com/yaoyuan/jiscuss/service/impl/UserDetailServiceImpl.java b/src/main/java/com/yaoyuan/jiscuss/service/impl/UserDetailServiceImpl.java new file mode 100644 index 0000000..629d951 --- /dev/null +++ b/src/main/java/com/yaoyuan/jiscuss/service/impl/UserDetailServiceImpl.java @@ -0,0 +1,59 @@ +package com.yaoyuan.jiscuss.service.impl; + +import com.yaoyuan.jiscuss.entity.Users; +import com.yaoyuan.jiscuss.service.IUsersService; +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.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author yaoyuan2.chu + * @Title: + * @Package com.yaoyuan.jiscuss.service.impl + * @Description: + * @date 2020/7/15 16:34 + */ +@Component +public class UserDetailServiceImpl implements UserDetailsService { + @Autowired + private IUsersService userInfoService; + + /** + * 需新建配置类注册一个指定的加密方式Bean,或在下一步Security配置类中注册指定 + */ + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + // 通过用户名从数据库获取用户信息 + Users userInfo = userInfoService.getByUsername(username); + if (userInfo == null) { + throw new UsernameNotFoundException("用户不存在"); + } + + // 得到用户角色 + String role = "admin"; + + // 角色集合 + List authorities = new ArrayList<>(); + // 角色必须以`ROLE_`开头,数据库中没有,则在这里加 + authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); + + return new User( + userInfo.getUsername(), + // 因为数据库是明文,所以这里需加密密码 + passwordEncoder.encode(userInfo.getPassword()), + authorities + ); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 59649f4..ce3580c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,6 +41,8 @@ spring: # 设置ftl文件路径 template-loader-path: - classpath:/templates + settings: + classic_compatible: true # 设置静态文件路径,js,css等 mvc: static-path-pattern: /static/** diff --git a/src/main/resources/static/js/user/header.js b/src/main/resources/static/js/user/header.js index 225351d..3800099 100644 --- a/src/main/resources/static/js/user/header.js +++ b/src/main/resources/static/js/user/header.js @@ -40,7 +40,7 @@ function loginOut() { console.log(data); if(data.flag){ $('#userlogin').html(''); - $('#userlogin').html('登录 & 注册'); + $('#userlogin').html('登录 & 注册'); window.alert(username + data.msg); username = null; }else{ @@ -52,9 +52,3 @@ function loginOut() { } -function loginmodel() { - $('.ui.modal.login') - .modal('show') - ; -} - diff --git a/src/main/resources/templates/header.ftl b/src/main/resources/templates/header.ftl index 185aa62..b53bfc0 100644 --- a/src/main/resources/templates/header.ftl +++ b/src/main/resources/templates/header.ftl @@ -30,53 +30,12 @@ <#if username??> ${username} 注销 <#else> - 登录 & 注册 + 登录 & 注册 - - + + + - - - \ No newline at end of file diff --git a/src/main/resources/templates/login.ftl b/src/main/resources/templates/login.ftl new file mode 100644 index 0000000..4531489 --- /dev/null +++ b/src/main/resources/templates/login.ftl @@ -0,0 +1,75 @@ + + +Jiscuss Demo + + + + + + + + + + + + + + +
+
+

+ +
+ 登录到账号 +
+

+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ ${msg} +
+
+ +
+ +
+ +
+ +
+ <——Jiscuss首页 新用户? 注册 +
+
+
+ + + + +