配置很简单,不做解释
server: port: 8080 spring: mvc: view: prefix: /pages/ suffix: .jsp datasource: url: jdbc:mysql:///security_authority?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC username: root password: driver-class-name: com.mysql.cj.jdbc.Driver mybatis: type-aliases-package: cn.lx.security.doamin configuration: #驼峰 map-underscore-to-camel-case: true logging: level: cn.lx.security: debug这个可以去log4j官网找
# Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategory=INFO, CONSOLE debug info warn error fatal log4j.rootCategory=debug, CONSOLE, LOGFILE # Set the enterprise logger category to FATAL and its only appender to CONSOLE. log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n # LOGFILE is set to be a File appender using a PatternLayout. log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=../logs/wlanapi/client.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n项目会自动将包标点,如此,web项目创建成功
将项目需要的jsp,css等资源复制到webapp下,使用代码生成工具生成三层结构,并实现对用户,角色,权限的增删改查,这个太简单了,而且不是我们的重点,这里我们就默认已经搭建好了。接下来我们就来实现对security的配置。
SysUser直接UserDetails接口,这样我们在loadUserByUsername方法中就不用创建UserDetails实现类User的对象了,而是直接使用SysUser,非常方便,注意,要添加一个额外的属性,就是该用户拥有的权限的list集合
@Data @Table(name = "sys_user") public class SysUser implements UserDetails { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) @Column(name = "id") private Integer id; @Column(name = "username") private String username; @Column(name = "password") private String password; @Column(name = "status") private Integer status; //权限的集合对象 private List<SysPermission> authorities; /** * Returns the authorities granted to the user. Cannot return <code>null</code>. * * @return the authorities, sorted by natural key (never <code>null</code>) */ @JsonIgnore @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } /** * Indicates whether the user's account has expired. An expired account cannot be * authenticated. * * @return <code>true</code> if the user's account is valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } /** * Indicates whether the user is locked or unlocked. A locked user cannot be * authenticated. * * @return <code>true</code> if the user is not locked, <code>false</code> otherwise */ @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } /** * Indicates whether the user's credentials (password) has expired. Expired * credentials prevent authentication. * * @return <code>true</code> if the user's credentials are valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } /** * Indicates whether the user is enabled or disabled. A disabled user cannot be * authenticated. * 4个布尔值必须全为true,用户才算认证通过,这几个上篇文档已经讲过了 * @return <code>true</code> if the user is enabled, <code>false</code> otherwise */ @JsonIgnore @Override public boolean isEnabled() { return status==1; } }我们让SysPermission实现GrantedAuthority接口,这样在loadUserByUsername方法中就不用了重新封装权限了
@Data @Table(name = "sys_permission") public class SysPermission implements GrantedAuthority { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) @Column(name = "ID") private Integer id; @Column(name = "permission_NAME") private String permissionName; @Column(name = "permission_url") private String permissionUrl; @Column(name = "parent_id") private String parentId; /** * If the <code>GrantedAuthority</code> can be represented as a <code>String</code> * and that <code>String</code> is sufficient in precision to be relied upon for an * access control decision by an {@link AccessDecisionManager} (or delegate), this * method should return such a <code>String</code>. * <p> * If the <code>GrantedAuthority</code> cannot be expressed with sufficient precision * as a <code>String</code>, <code>null</code> should be returned. Returning * <code>null</code> will require an <code>AccessDecisionManager</code> (or delegate) * to specifically support the <code>GrantedAuthority</code> implementation, so * returning <code>null</code> should be avoided unless actually required. * * @return a representation of the granted authority (or <code>null</code> if the * granted authority cannot be expressed as a <code>String</code> with sufficient * precision). * @JsonIgnore 这个注解这个注解的作用是在序列化的时候忽略authority字段 */ @JsonIgnore @Override public String getAuthority() { return permissionName; } }继承UserDetailsService接口,并实现
@Service public class IUserServiceImpl implements IUserService { @Autowired private UserMapper userMapper; /** * Locates the user based on the username. In the actual implementation, the search * may possibly be case sensitive, or case insensitive depending on how the * implementation instance is configured. In this case, the <code>UserDetails</code> * object that comes back may have a username that is of a different case than what * was actually requested.. * * @param username the username identifying the user whose data is required. * @return a fully populated user record (never <code>null</code>) * @throws UsernameNotFoundException if the user could not be found or the user has no * GrantedAuthority */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //这里是不是很简单 SysUser sysUser = userMapper.findByUsername(username); if (null==sysUser){ throw new RuntimeException("没有此用户"); } return sysUser; } }所以我们可以直接这样,不理解的自己去查一下资料
@Select("select * from sys_user where username=#{username}") @Results({@Result(id = true, property = "id", column = "id"), @Result(property = "authorities", column = "id", javaType = List.class, many = @Many(select = "cn.lx.security.dao.RermissionMapper.findByUid"))}) public SysUser findByUsername(String username);然后我们在RermissionMapper定义一个方法
//根据用户id查询该用户所拥有的权限 @Select("SELECT * FROM sys_permission WHERE ID IN(" + "SELECT PID FROM sys_role_permission WHERE RID IN(" + "SELECT RID FROM sys_user_role WHERE uid=#{uid}" + "))") public List<SysPermission> findByUid(Integer uid);新建一个security的配置类
@Configuration @EnableWebSecurity //spring的安全注解,推荐使用 @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private IUserService iUserService; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; /** * Used by the default implementation of {@link #authenticationManager()} to attempt * to obtain an {@link AuthenticationManager}. If overridden, the * {@link AuthenticationManagerBuilder} should be used to specify the * {@link AuthenticationManager}. * * <p> * The {@link #authenticationManagerBean()} method can be used to expose the resulting * {@link AuthenticationManager} as a Bean. The {@link #userDetailsServiceBean()} can * be used to expose the last populated {@link UserDetailsService} that is created * with the {@link AuthenticationManagerBuilder} as a Bean. The * {@link UserDetailsService} will also automatically be populated on * {@link HttpSecurity#getSharedObject(Class)} for use with other * {@link SecurityContextConfigurer} (i.e. RememberMeConfigurer ) * </p> * * <p> * For example, the following configuration could be used to register in memory * authentication that exposes an in memory {@link UserDetailsService}: * </p> * * <pre> * @Override * protected void configure(AuthenticationManagerBuilder auth) { * auth * // enable in memory based authentication with a user named * // "user" and "admin" * .inMemoryAuthentication().withUser("user").password("password").roles("USER").and() * .withUser("admin").password("password").roles("USER", "ADMIN"); * } * * // Expose the UserDetailsService as a Bean * @Bean * @Override * public UserDetailsService userDetailsServiceBean() throws Exception { * return super.userDetailsServiceBean(); * } * * </pre> * * @param auth the {@link AuthenticationManagerBuilder} to use * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //在内存中注册一个账号 //auth.inMemoryAuthentication().withUser("user").password("{noop}123").roles("USER"); //连接数据库,使用数据库中的账号 auth.userDetailsService(iUserService).passwordEncoder(bCryptPasswordEncoder); } /** * Override this method to configure {@link WebSecurity}. For example, if you wish to * ignore certain requests. * 这些资源可以直接访问,不需要认证 * @param web */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/img/**", "/plugins/**", "/failer.jsp", "/login.jsp"); } /** * Override this method to configure the {@link HttpSecurity}. Typically subclasses * should not invoke this method by calling super as it may override their * configuration. The default configuration is: * * <pre> * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); * </pre> * 读过上一篇文档的应该对这些配置有所了解 * @param http the {@link HttpSecurity} to modify * @throws Exception if an error occurs */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() //禁用csrf .authorizeRequests() //所有请求都需要认证 .anyRequest().authenticated() .and() //配置登录相关 .formLogin().loginPage("/login.jsp").loginProcessingUrl("/login").defaultSuccessUrl("/index.jsp").failureForwardUrl("/failer.jsp").permitAll() .and() //配置注销相关 .logout().logoutUrl("/logout").logoutSuccessUrl("/login.jsp").invalidateHttpSession(true).permitAll(); }jsp上菜单控制上一篇文档已经给大家看过了,这里就不展示了
在resources下面新建一个static文件夹,静态资源全部放这里面,具体的见下图
只需要注意两个地方,其他的和jsp差不多
(1)thymeleaf的约束
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org">(2)静态资源的路径
不需要写static路径,程序会自动识别static下的静态资源
<link rel="stylesheet" href="/plugins/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/plugins/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="/plugins/ionicons/css/ionicons.min.css"> <link rel="stylesheet" href="/plugins/adminLTE/css/AdminLTE.css"> <link rel="stylesheet" href="/plugins/iCheck/square/blue.css">必须要写一个控制器,通过controller访问页面,我们可以写一个mvc的配置类
@Configuration //不要使用这个注解,他会禁用springboot的自动装配,导致很多默认配置失效 //@EnableWebMvc public class MvcConfig implements WebMvcConfigurer { /** * Configure simple automated controllers pre-configured with the response * status code and/or a view to render the response body. This is useful in * cases where there is no need for custom controller logic -- e.g. render a * home page, perform simple site URL redirects, return a 404 status with * HTML content, a 204 with no content, and more. * * @param registry */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/loginPage").setViewName("login"); } }还要修改登录页面
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() //.antMatchers("/css/**", "/img/**", "/plugins/**").permitAll() .anyRequest().authenticated() .and() //.formLogin().loginPage("/login.jsp").loginProcessingUrl("/login").defaultSuccessUrl("/index.jsp").failureForwardUrl("/failer.jsp").permitAll() //我们这里是前后端分离,不需要默认跳转页面和登录失败的页面 .formLogin().loginPage("/loginPage").loginProcessingUrl("/login").permitAll() .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/loginPage").invalidateHttpSession(true).permitAll(); }